深刻React高階組件(HOC)

什麼是HOC?

HOC(全稱Higher-order component)是一種React的進階使用方法,主要仍是爲了便於組件的複用。HOC就是一個方法,獲取一個組件,返回一個更高級的組件。html

何時使用HOC?

在React開發過程當中,發現有不少狀況下,組件須要被"加強",好比說給組件添加或者修改一些特定的props,一些權限的管理,或者一些其餘的優化之類的。而若是這個功能是針對多個組件的,同時每個組件都寫一套相同的代碼,明顯顯得不是很明智,因此就能夠考慮使用HOC。react

栗子:react-redux的connect方法就是一個HOC,他獲取wrappedComponent,在connect中給wrappedComponent添加須要的props。git

HOC的簡單實現

HOC不只僅是一個方法,確切說應該是一個組件工廠,獲取低階組件,生成高階組件。github

一個最簡單的HOC實現是這個樣子的:redux

function HOCFactory(WrappedComponent) {
  return class HOC extends React.Component {
    render(){
      return <WrappedComponent {...this.props} />
    }
  }
}
複製代碼

HOC能夠作什麼?

  • 代碼複用,代碼模塊化
  • 增刪改props
  • 渲染劫持

其實,除了代碼複用和模塊化,HOC作的其實就是劫持,因爲傳入的wrappedComponent是做爲一個child進行渲染的,上級傳入的props都是直接傳給HOC的,因此HOC組件擁有很大的權限去修改props和控制渲染。bash

增刪改props

能夠經過對傳入的props進行修改,或者添加新的props來達到增刪改props的效果。app

好比你想要給wrappedComponent增長一個props,能夠這麼搞:模塊化

function control(wrappedComponent) {
  return class Control extends React.Component {
    render(){
      let props = {
        ...this.props,
        message: "You are under control"
      };
      return <wrappedComponent {...props} />
    }
  }
}
複製代碼

這樣,你就能夠在你的組件中使用message這個props:優化

class MyComponent extends React.Component {
  render(){
    return <div>{this.props.message}</div>
  }
}

export default control(MyComponent);
複製代碼

渲染劫持

這裏的渲染劫持並非你能控制它渲染的細節,而是控制是否去渲染。因爲細節屬於組件內部的render方法控制,因此你沒法控制渲染細節。ui

好比,組件要在data沒有加載完的時候,現實loading...,就能夠這麼寫:

function loading(wrappedComponent) {
  return class Loading extends React.Component {
    render(){
      if(!this.props.data) {
        return <div>loading...</div>
      }
      return <wrappedComponent {...props} />
    }
  }
}
複製代碼

這個樣子,在父級沒有傳入data的時候,這一起就只會顯示loading...,不會顯示組件的具體內容

class MyComponent extends React.Component {
  render(){
    return <div>{this.props.data}</div>
  }
}

export default control(MyComponent);
複製代碼

HOC有什麼用例?

React Redux

最經典的就是React Redux的connect方法(具體在connectAdvanced中實現)。

經過這個HOC方法,監聽redux store,而後把下級組件須要的state(經過mapStateToProps獲取)和action creator(經過mapDispatchToProps獲取)綁定到wrappedComponent的props上。

logger和debugger

這個是官網上的一個示例,能夠用來監控父級組件傳入的props的改變:

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log(`WrappedComponent: ${WrappedComponent.displayName}, Current props: `, this.props);
      console.log(`WrappedComponent: ${WrappedComponent.displayName}, Next props: `, nextProps);
    }
    render() {
      // Wraps the input component in a container, without mutating it. Good!
      return <WrappedComponent {...this.props} />;
    }
  }
}
複製代碼

頁面權限管理

能夠經過HOC對組件進行包裹,當跳轉到當前頁面的時候,檢查用戶是否含有對應的權限。若是有的話,渲染頁面。若是沒有的話,跳轉到其餘頁面(好比無權限頁面,或者登錄頁面)。

也能夠給當前組件提供權限的API,頁面內部也能夠進行權限的邏輯判斷。

使用HOC須要注意什麼?

儘可能不要隨意修改下級組件須要的props 之因此這麼說,是由於修改父級傳給下級的props是有必定風險的,可能會形成下級組件發生錯誤。好比,本來須要一個name的props,可是在HOC中給刪掉了,那麼下級組件或許就沒法正常渲染,甚至報錯。

Ref沒法獲取你想要的ref

之前你在父組件中使用<component ref="component"/>的時候,你能夠直接經過this.refs.component進行獲取。可是由於這裏的component通過HOC的封裝,已是HOC裏面的那個component了,因此你沒法獲取你想要的那個ref(wrappedComponent的ref)

要解決這個問題,這裏有兩個方法:

a) 像React Redux的connect方法同樣,在裏面添加一個參數,好比withRef,組件中檢查到這個flag了,就給下級組件添加一個ref,並經過getWrappedInstance方法獲取。

栗子:

function HOCFactory(wrappedComponent) {
  return class HOC extends React.Component {
    getWrappedInstance = ()=>{
      if(this.props.widthRef) {
        return this.wrappedInstance;
      }
    }

    setWrappedInstance = (ref)=>{
      this.wrappedInstance = ref;
    }

    render(){
      let props = {
        ...this.props
      };

      if(this.props.withRef) {
        props.ref = this.setWrappedInstance;
      }

      return <wrappedComponent {...props} />
    }
  }
}

export default HOCFactory(MyComponent);
複製代碼

這樣子你就能夠在父組件中這樣獲取MyComponent的ref值了。

class ParentCompoent extends React.Component {
  doSomethingWithMyComponent(){
    let instance = this.refs.child.getWrappedInstance();
    // ....
  }

  render(){
    return <MyComponent ref="child" withRef />
  }
}
複製代碼

b) 還有一種方法,在官網中有提到過: 父級經過傳遞一個方法,來獲取ref,具體看栗子:

先看父級組件:

class ParentCompoent extends React.Component {
  getInstance = (ref)=>{
    this.wrappedInstance = ref;
  }

  render(){
    return <MyComponent getInstance={this.getInstance} />
  }
}
複製代碼

HOC裏面把getInstance方法看成ref的方法傳入就好

function HOCFactory(wrappedComponent) {
  return class HOC extends React.Component {
    render(){
      let props = {
        ...this.props
      };

      if(typeof this.props.getInstance === "function") {
        props.ref = this.props.getInstance;
      }

      return <wrappedComponent {...props} />
    }
  }
}

export default HOCFactory(MyComponent);
複製代碼

Component上面綁定的Static方法會丟失

好比,你原來在Component上面綁定了一些static方法MyComponent.staticMethod = o=>o。可是因爲通過HOC的包裹,父級組件拿到的已經不是原來的組件了,因此固然沒法獲取到staticMethod方法了。

官網上的示例:

// 定義一個static方法
WrappedComponent.staticMethod = function() {/*...*/}
// 利用HOC包裹
const EnhancedComponent = enhance(WrappedComponent);

// 返回的方法沒法獲取到staticMethod
typeof EnhancedComponent.staticMethod === 'undefined' // true
複製代碼

這裏有一個解決方法,就是hoist-non-react-statics組件,這個組件會自動把全部綁定在對象上的非React方法都綁定到新的對象上:

import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}
複製代碼

結束語

當你須要作React插件的時候,HOC模型是一個很實用的模型。

但願這篇文章能幫你對HOC有一個大概的瞭解和啓發。

另外,這篇medium上的文章會給你更多的啓發,在這篇文章中,我這裏講的被分爲Props Proxy HOC,還有另一種Inheritance Inversion HOC,強烈推薦看一看。

相關文章
相關標籤/搜索