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

前言

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

1.爲何須要高階組件?

2.高階組件是什麼?

3.如何實現高階組件?


1.爲何須要高階組件?

這個問題很簡單,爲何咱們須要react/vue/angular?使用框架最核心的緣由之一就是提升開發效率,能早點下班。同理,react高階組件可以讓咱們寫出更易於維護的react代碼,能再早點下班~react

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

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

2.高階組件是什麼?

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

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

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屬性: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能夠拿到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),改變組件的行爲。

相關文章
相關標籤/搜索