"高階"二字聽起來很是唬人,由於大學高數課上的高階方程讓人抓狂,從而讓第一次接觸"高階組件"概念的人們誤覺得又是什麼高深的思想和複雜的邏輯。但相信在你學習完成後和生產環境大量使用過程當中,就會發現這個所謂"高階組件"真的一點也不高階,很是簡單易懂。本文經過回答三個問題帶你深刻淺出React高階組件。vue
這個問題很簡單,爲何咱們須要react/vue/angular?使用框架最核心的緣由之一就是提升開發效率,能早點下班。同理,react高階組件可以讓咱們寫出更易於維護的react代碼,能再早點下班~react
舉個栗子,ES6支持使用import/export的方式拆分代碼功能和模塊,避免一份文件裏面出現"成坨"的代碼。同理對於複雜的react組件,若是這個組件有幾十個自定義的功能函數,天然要進行拆分,否則又成了"一坨"組件,那麼該如何優雅地拆分組件呢?react高階組件應運而生。chrome
在使用ES5編寫react代碼時,可使用Mixin這一傳統模式進行拆分。新版本的react全面支持ES6並提倡使用ES6編寫jsx,同時取消了Mixin。所以高階組件愈來愈受到開源社區的重視,例如redux等知名第三方庫都大量使用了高階組件。redux
回答這個問題前,咱們先看下本文的封面圖,爲何筆者用初中生就掌握的一元一次函數來表明這篇文章呢?很顯然,高階函數就是形如y=kx+b
的東西,x
是咱們想要改造的原組件,y
就是改造事後輸出的組件。那具體是怎麼改造的呢?k
和b
就是改造的方法。這就是高階組件的基本原理,是否是一點也不高階~bash
再舉個栗子相信更能讓你明白:咱們寫代碼須要進行加法計算,因而咱們把加法計算的方法單獨抽出來寫成一個加法函數,這個加法函數能夠在各處調用使用,從而減小了工做量和代碼量。而咱們獨立出來的這個能夠隨處使用的加法函數,類比地放在react裏,就是高階組件。app
從上面的問題回答中,咱們知道了:高階組件其實就是處理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
屬性:ui
上述代碼使用了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
),改變組件的行爲。