帶着三個問題深入淺出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
就是改造過後輸出的組件。那具體是怎麼改造的呢?k
和b
就是改造的方法。這就是高階組件的基本原理,是不是一點也不高階~
再舉個栗子相信更能讓你明白:我們寫代碼需要進行加法計算,於是我們把加法計算的方法單獨抽出來寫成一個加法函數,這個加法函數可以在各處調用使用,從而減少了工作量和代碼量。而我們獨立出來的這個可以隨處使用的加法函數,類比地放在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
可以拿到state
和props
進行修改,從而改變組件的行爲,也就是所謂的"渲染劫持"。可以說,通過反向繼承方法實現的高階組件相比於屬性代理實現的高階組件,功能更強大,個性化程度更高,適應更多的場景。
如上的代碼,我們可以看到:
第一:this.test1()
輸出了9。爲什麼?
因爲在ES6中,super
作爲對象調用父類方法時,super
綁定子類的this
。故執行super.render()
時OriginComponent
中的this
指向的是HOC
組件,所以能夠成功地執行test1
函數。
第二:控制檯輸出的是1而不是2。爲什麼?
首先,decorator是在代碼編譯階段執行,故HOC
的render
方法在OriginComponent
的render
方法之前執行。並且子組件HOC
是繼承於父組件OriginComponent
,兩者具有繼承關係HOC.__proto__ === OriginComponent
,當執行componentDidMount
方法時,子組件已存在該方法,故執行完畢後結束,不再根據__proto__
向上繼續尋找。如果我們將子組件HOC
中的componentDidMount
方法去掉,那麼控制檯將輸出2。
當我們有多個高階組件需要同時增強一個組件時該怎麼辦呢?我們可以這樣寫:
@fun1 @fun2 @fun3 class OriginComponent extends Component { ... }
也可以使用lodash
的flowRight
方法:
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
,可以拿到state
和props
進行渲染劫持(k
),改變組件的行爲。
原地址:https://juejin.im/post/59818a485188255694568ff2
相關鏈接:https://www.jianshu.com/p/0aae7d4d9bc1 簡書