相信很多看過一些框架或者是類庫的人都有印象,一個函數叫什麼creator或者是什麼什麼createToFuntion,老是接收一個函數,來返回另外一個函數。這是一個高階函數,它能夠接收函數能夠當參數,也能夠當返回值,這就是函數式編程。像柯里化、裝飾器模式、高階組件,都是相通的,一個道理。react
本文重點是React高階組件,要理解高階組件,不得不說函數式編程。編程
函數式編程是一種編程模式,在這種編程模式種最經常使用函數和表達式,函數式編程把函數做爲一等公民,強調從函數的角度考慮問題,函數式編程傾向用一系列嵌套的函數來解決問題。redux
簡單寫個例子設計模式
function OCaml () { console.log('I\'m FP language OCaml') } function clojure() { console.log('I\'m FP language clojure') }
如今想在每條console語句先後各加一條console語句,若是在每一個函數都加上console語句,會產生沒必要要的耦合,因此高階函數就派上了用場。app
function FuncWrapper(func) { return function () { console.log('before') func() console.log('after') } } var OCaml = FuncWrapper(OCaml) var clojure = FuncWrapper(clojure)
咱們寫了一個函數FuncWrapper,該函數接一個函數做爲參數,將參數函數裝飾了一層,返回出去,減小了代碼耦合。在設計模式中稱這種模式爲裝飾器或裝飾者模式。框架
固然函數式編程的好處不止這一條,有些人吹捧OCaml,clojure, scala等FP語言特性好比:純函數無反作用、不變的數據、流計算模式、尾遞歸、柯里化等等。dom
在React中,高階組件HOC就至關於這麼一個FuncWrapper,傳入一個組件,返回被包裝或者被處理的另外一個組件。函數式編程
上邊已經簡單說過了什麼是高階組件,其實本質上是一個類工廠。先舉個例在再說函數
第一個組件佈局
import React from 'react' export default class OCaml extends React.Component { constructor (props) { super(props) this.changeHandle = this.changeHandle.bind(this) } changeHandle (value) { console.log(value) } render () { return ( <div> <h2>I'm OCaml</h2> <input type="text" onchange={value => this.changeHandle(value)}/> </div> ) } }
第二個組件
import React from 'react' export default class Clojure extends React.Component { constructor (props) { super(props) this.changeHandle = this.changeHandle.bind(this) } changeHandle (value) { console.log(value) } render () { return ( <div> <h2>I'm Clojure</h2> <input type="text" onchange={value => this.changeHandle(value)}/> </div> ) } }
有兩個不相同的組件,可是有部分功能重合,就是那個changeHandle函數,理解了高階函數,再解決這類問題就不難了吧?不仍是同樣嗎?
import React from 'react' export default function CompWrapper (Component) { return class WarpComponent extends React.Component { constructor (props) { super(props) this.handleChange = this.handleChange.bind(this) } handleChange (value) { console.log(value) } render () { return <Component handleChange={this.handleChange} {...this.props}></Component> } } } OCaml = CompWrapper(OCaml) Clojure = CompWrapper(Clojure)
這是一個最簡單的高階組件。注意,再高階組件的返回包裝好的組件的時候,咱們將高階組件的props展開並傳入包裝好的組件中,這是確保給高階組件的props也能給到被包裝的組件上。
高階組件的通途不少,能夠用來,代碼複用,邏輯抽象,抽離底層代碼,渲染劫持,更改state、更改props等等。
咱們主要說一下兩種功能的React高階組件:屬性代理、反向繼承。
很好說了,上面已經提到過了,再來一遍,高階組件將它收到的props傳遞給被包裝的組件,所叫屬性代理
export default function CompWrapper (Component) { return class WarpComponent extends React.Component { render () { return <Component {...this.props}></Component> } } }
例子是增長props的,其餘的相似,都是在在高階組件內部加以處理。
import React from 'react' export default function CompWrapper (Component) { return class WarpComponent extends React.Component { say () { console.log('我是被高階組件包裝過的組件!') } newProps = { isLogin: true, msgList: [1,2,3,4,5] } render () { return <Component say={this.say} {...this.props} {...this.newProps}></Component> } } }
包裝好的組件能夠用this.props.say調用say方法,能夠用this.props.isLogin判斷登錄狀態等等。
咱們能夠經過props和回調函數來抽象state
import React from 'react' export function CompWrapper (Component) { return class WarpComponent extends React.Component { constructor (props) { super(props) this.state = { inputValue: '暫時還沒喲' } this.changeHandle = this.changeHandle.bind(this) } changeHandle (event) { this.setState({ inputValue: event.target.value }) } render () { return <Component {...this.props} inputValue={this.state.inputValue} changeHandle={this.changeHandle}></Component> } } }
這個高階組件將一切數據都綁定到了本身的身上,只須要出觸發被包裝組件的特定事件,就將改變本身的state,再將本身的state經過props傳遞給被包裝組件。
@CompWrapper export class InputComp extends React.Component { render () { return ( <div> <h2>{this.props.inputValue}</h2> <input type="text" onChange={this.props.changeHandle}/> </div> ) } }
這裏的input就成了徹底受控的組件。注意:在定義組件的語句上邊寫上@CompWrapper是和InputComp = CompWrapper(InputComp)
做用是同樣的。@操做符是ES7的decorator,也就是裝飾器。
從你的 render 方法中返回你的 UI 結構後,你會發現你想要「伸手」調用從 render 返回的組件實例的方法,咱們能夠經過ref獲取組件的實例,可是想讓ref生效,必須先通過一次正常的渲染來使ref獲得計算,怎麼先讓組件通過一次正常的渲染呢?高階組件又來了,明白了嗎?高階組件的render返回了被包裝的組件,而後咱們就能夠經過ref獲取這個組件的實例了。
import React from 'react' export function CompWrapper (Component) { return class WarpComponent extends React.Component { proc(wrappedComponentInstance) { wrappedComponentInstance.say() } render () { const props = Object.assign({}, this.props, {ref: this.proc.bind(this)}) return <Component {...props}></Component> } } } @CompWrapper export class InputComp extends React.Component { say () { console.log('I\'m InputComp') } render () { return ( <button onClick={()=>{console.log(this.props)}}>點擊</button> ) } }
當被包裝的組件被渲染後,就能夠執行本身實例的方法了,由於計算ref這件事已經由高階組件作完了。
這個很好理解,若是想把佈局什麼的和組件結合到一塊兒,使用高階組件是一個辦法。
import React from 'react' export function CompWrapper (Component) { return class WarpComponent extends React.Component { render () { return ( <div style={{marginTop: 100}}> <Component {...this.props}/> </div> ) } } }
爲何叫反向繼承,是高階組件繼承被包裝組件,按照咱們想的被包裝組件繼承高階組件。
import React from 'react' export function CompWrapper (Component) { return class WarpComponent extends Component { render () { return super.render() } } }
反向代理主要用來作渲染劫持
所謂的渲染劫持,就是最後組件所渲染出來的東西或者咱們叫React Element徹底由高階組件來決定,經過因此咱們能夠對任意一個React Element的props進行操做;咱們也能夠操做React Element的Child。
import React from 'react' export function CompWrapper (Component) { return class WarpComponent extends Component { render () { const reactElm = super.render() let newProps = {} if (reactElm.type === 'input') { newProps = {value: '這是一個input'} } const props = Object.assign({}, reactElm.props, newProps) const newReactElm = React.cloneElement(reactElm, props, reactElm.props.child) return newReactElm } } }
這個例子,判斷組件的頂層元素是否爲一個input,若是是的話,經過cloneElement這個方法來克隆出一個同樣的組件,並將新的props傳入,這樣input就有值了。
用過React-Redux的人可能會有印象,使用connect能夠將react和redux關聯起來,這裏的connect就是一個高階組件,想到這,就很容易想出connect高階組件是怎麼實現了,我會在寫一篇隨筆,本身實現一個redux、connect、midlleware還有thunk。