帶着三個問題深入淺出React高階組件

帶着三個問題深入淺出React高階組件

"高階"二字聽起來非常唬人,因爲大學高數課上的高階方程讓人抓狂,從而讓第一次接觸"高階組件"概念的人們誤以爲又是什麼高深的思想和複雜的邏輯。但相信在你學習完成後和生產環境大量使用過程中,就會發現這個所謂"高階組件"真的一點也不高階,非常簡單易懂。本文通過回答三個問題帶你深入淺出React高階組件。

1.爲什麼需要高階組件?

2.高階組件是什麼?

3.如何實現高階組件?


1.爲什麼需要高階組件?

這個問題很簡單,爲什麼我們需要react/vue/angular?使用框架最核心的原因之一就是提高開發效率,能早點下班。同理,react高階組件能夠讓我們寫出更易於維護的react代碼,能再早點下班~

舉個栗子,ES6支持使用import/export的方式拆分代碼功能和模塊,避免一份文件裏面出現"成坨"的代碼。同理對於複雜的react組件,如果這個組件有幾十個自定義的功能函數,自然要進行拆分,不然又成了"一坨"組件,那麼該如何優雅地拆分組件呢?react高階組件應運而生。

在使用ES5編寫react代碼時,可以使用Mixin這一傳統模式進行拆分。新版本的react全面支持ES6並提倡使用ES6編寫jsx,同時取消了Mixin。因此高階組件越來越受到開源社區的重視,例如redux等知名第三方庫都大量使用了高階組件。

2.高階組件是什麼?

回答這個問題前,我們先看下本文的封面圖,爲什麼筆者用初中生就掌握的一元一次函數來代表這篇文章呢?很顯然,高階函數就是形如y=kx+b的東西,x是我們想要改造的原組件,y就是改造過後輸出的組件。那具體是怎麼改造的呢?kb就是改造的方法。這就是高階組件的基本原理,是不是一點也不高階~

再舉個栗子相信更能讓你明白:我們寫代碼需要進行加法計算,於是我們把加法計算的方法單獨抽出來寫成一個加法函數,這個加法函數可以在各處調用使用,從而減少了工作量和代碼量。而我們獨立出來的這個可以隨處使用的加法函數,類比地放在react裏,就是高階組件。

3.如何實現高階組件?

從上面的問題回答中,我們知道了:高階組件其實就是處理react組件的函數。那麼我們如何實現一個高階組件?有兩種方法:

1.屬性代理
2.反向繼承

第一種方法屬性代理是最常見的實現方式,將被處理組件的props和新的props一起傳遞給新組件,代碼如下:

//WrappedComponent爲被處理組件 function HOC(WrappedComponent){     return class HOC extends Component {         render(){             const newProps = {type:'HOC'};             return <div>                 <WrappedComponent {...this.props} {...newProps}/>             </div>         }     } } @HOC class OriginComponent extends Component {     render(){         return <div>這是原始組件</div>     } } //const newComponent = HOC(OriginComponent)

屬性代理聽起來好像很麻煩,然而從代碼中看,就是使用HOC這個函數,向被處理的組件WrappedComponent上面添加一些屬性,並返回一個包含原組件的新組件。從chrome調試臺上我們可以看到原始組件已經被包裹起來了並具有type屬性:



上述代碼使用了ES7的decorator裝飾器來實現對OriginComponent組件的裝飾和增強,或者使用註釋中的函數方法一樣可以達到相同的效果。

使用屬性代理的好處就是,可以把常用的方法獨立出來並多次複用。比如我們實現了一個加法函數,那麼我們把加法函數改造成形如上述HOC函數的形式,之後對其他組件進行包裹,就可以在組件裏使用這個方法了。

第二種方法反向繼承就有意思了,先看代碼:

function HOC(WrappedComponent){     return class HOC extends WrappedComponent {         //繼承了傳入的組件         test1(){             return this.test2() + 5;         }         componentDidMount(){             console.log('1');             this.setState({number:2});         }         render(){             //使用super調用傳入組件的render方法             return super.render();         }     } } @HOC class OriginComponent extends Component {     constructor(props){         super(props);         this.state = {number:1}     }     test2(){         return 4;     }     componentDidMount(){         console.log('2');     }     render(){         return (             <div>                 {this.state.number}{'and'}                 {this.test1()}                 這是原始組件             </div>         )     } } //const newComponent = HOC(OriginComponent)

代碼看完我們可能還有點懵,那我們先來剖析關鍵詞"繼承"。何謂繼承?新生成的HOC組件通過extends關鍵字,獲得了傳入組件OriginComponent所有的屬性和方法,是謂"繼承"。也就是說繼承之後,HOC組件能夠實現OriginComponent組件的全部功能,而且,HOC可以拿到stateprops進行修改,從而改變組件的行爲,也就是所謂的"渲染劫持"。可以說,通過反向繼承方法實現的高階組件相比於屬性代理實現的高階組件,功能更強大,個性化程度更高,適應更多的場景。

如上的代碼,我們可以看到:



第一:this.test1()輸出了9。爲什麼?
因爲在ES6中,super作爲對象調用父類方法時,super綁定子類的this。故執行super.render()OriginComponent中的this指向的是HOC組件,所以能夠成功地執行test1函數。

第二:控制檯輸出的是1而不是2。爲什麼?
首先,decorator是在代碼編譯階段執行,故HOCrender方法在OriginComponentrender方法之前執行。並且子組件HOC是繼承於父組件OriginComponent,兩者具有繼承關係HOC.__proto__ === OriginComponent,當執行componentDidMount方法時,子組件已存在該方法,故執行完畢後結束,不再根據__proto__向上繼續尋找。如果我們將子組件HOC中的componentDidMount方法去掉,那麼控制檯將輸出2。

當我們有多個高階組件需要同時增強一個組件時該怎麼辦呢?我們可以這樣寫:

@fun1 @fun2 @fun3 class OriginComponent extends Component {     ... }

也可以使用lodashflowRight方法:

const enchance = lodash.flowRight(fun1,fun2,fun3); @enchance class OriginComponent extends Component {     ... }

因爲fun1 fun2 fun3都是處理類的函數,只要實現按順序依次對類進行處理即可。

以上就是關於高階組件實現方式的全部內容。爲了查缺補漏,官方文檔中有兩條建議很中肯,在這裏摘抄給大家:



一句話總結,爲了避免在調試時,因爲高階組件的存在而導致滿屏的HOC(以上述代碼爲例),可以設置類的displayName屬性修改組件的名稱。



如果你對ES6中的繼承非常瞭解的話,那理解上述文字應該非常簡單。ES6的繼承中,子類不能繼承父類的靜態方法,即使用static關鍵字定義的方法。如果子類想使用,那麼一定要copy之後才能使用。

總結一下,高階組件其實就是處理組件的函數,他有兩種實現方式:
一是屬性代理,類似於一元一次方程的y = x + b,輸入x是原組件,參數b是你要添加或刪除的屬性/方法,y是最終輸出的組件。
二是反向繼承,類似於一元一次方程的y = kx + b,可以拿到stateprops進行渲染劫持(k),改變組件的行爲。

原地址:https://juejin.im/post/59818a485188255694568ff2

相關鏈接:https://www.jianshu.com/p/0aae7d4d9bc1  簡書


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章