HOC(全稱Higher-order component)是一種React的進階使用方法,主要仍是爲了便於組件的複用。HOC就是一個方法,獲取一個組件,返回一個更高級的組件。javascript
在React開發過程當中,發現有不少狀況下,組件須要被"加強",好比說給組件添加或者修改一些特定的props,一些權限的管理,或者一些其餘的優化之類的。而若是這個功能是針對多個組件的,同時每個組件都寫一套相同的代碼,明顯顯得不是很明智,因此就能夠考慮使用HOC。html
栗子:react-redux的connect方法就是一個HOC,他獲取wrappedComponent,在connect中給wrappedComponent添加須要的props。java
HOC不只僅是一個方法,確切說應該是一個組件工廠,獲取低階組件,生成高階組件。react
一個最簡單的HOC實現是這個樣子的:git
function HOCFactory(WrappedComponent) { return class HOC extends React.Component { render(){ return <WrappedComponent {...this.props} /> } } }
其實,除了代碼複用和模塊化,HOC作的其實就是劫持,因爲傳入的wrappedComponent是做爲一個child進行渲染的,上級傳入的props都是直接傳給HOC的,因此HOC組件擁有很大的權限去修改props和控制渲染。github
能夠經過對傳入的props進行修改,或者添加新的props來達到增刪改props的效果。redux
好比你想要給wrappedComponent增長一個props,能夠這麼搞:app
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方法控制,因此你沒法控制渲染細節。函數
好比,組件要在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);
最經典的就是React Redux的connect方法(具體在connectAdvanced中實現)。
經過這個HOC方法,監聽redux store,而後把下級組件須要的state(經過mapStateToProps獲取)和action creator(經過mapDispatchToProps獲取)綁定到wrappedComponent的props上。
這個是官網上的一個示例,能夠用來監控父級組件傳入的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,頁面內部也能夠進行權限的邏輯判斷。
原本準備把詳細代碼當個栗子貼出來的,結果忽然想到公司保密協議,因此。。。
之因此這麼說,是由於修改父級傳給下級的props是有必定風險的,可能會形成下級組件發生錯誤。好比,本來須要一個name的props,可是在HOC中給刪掉了,那麼下級組件或許就沒法正常渲染,甚至報錯。
之前你在父組件中使用<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);
感謝@wmzy的指出,在上面的兩個方法getInstance
和setWrappedInstance
,因爲ES6 class
的寫法並不會自動綁定this
,因此須要用bind(this)
到兩個方法上,確保this
的正確性。或者使用箭頭函數來寫兩個方法,ES6的箭頭函數會自動綁定this
。
好比,你原來在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,強烈推薦看一看。