帶著三個問題深入淺出React高階元件

ssssyoki發表於2017-08-16

前言

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

帶著三個問題深入淺出React高階元件

上述程式碼使用了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進行修改,從而改變元件的行為,也就是所謂的"渲染劫持"。可以說,通過反向繼承方法實現的高階元件相比於屬性代理實現的高階元件,功能更強大,個性化程度更高,適應更多的場景。

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

帶著三個問題深入淺出React高階元件

第一: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都是處理類的函式,只要實現按順序依次對類進行處理即可。

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

帶著三個問題深入淺出React高階元件

一句話總結,為了避免在除錯時,因為高階元件的存在而導致滿屏的HOC(以上述程式碼為例),可以設定類的displayName屬性修改元件的名稱。

帶著三個問題深入淺出React高階元件

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

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

相關文章