React 高階組件

高階組件是對既有組件進行包裝,以增強既有組件的功能。其核心實現(xiàn)是一個無狀態(tài)組件(函數(shù)),接收另一個組件作為參數(shù),然后返回一個新的功能增強的組件。
看一個最簡單的例子:
App.js:

import React,{ PureComponent } from "react";

const DisplayUser = (props) => {
    return(
        <div>
            姓名:{ props.username }
        </div>
    );
} 

const HOC = (InnerComponent) => {
    return class HOCComponent extends PureComponent{
        render(){
            const { username } = this.props;
            return(
                <InnerComponent username = { username } />
            );
        }
    }
}

export default HOC(DisplayUser);

index.js:

import React from "react";
import ReactDOM from "react-dom";
import HOC from "./App";

ReactDOM.render(<HOC username = "Mike" birth = "1999-09-09" />,document.getElementById("root"));

HOC 函數(shù)接受一個組件作為參數(shù),返回一個新的組件,新組件中渲染了參數(shù)組件,這樣就可以對參數(shù)組件做一些配置,例如本例的過濾 props,只傳遞給參數(shù)組件合法的 props,不傳遞非法的 props。

高階組件的分類

高階組件分為兩種類型:

  • 代理型高階組件
  • 繼承型高階組件

上面的例子中的高階組件類型就是代理型高階組件。
二者的主要區(qū)別是對參數(shù)組件的操作上,代理型高階組件是返回一個新的組件類,該類繼承于根組件(Component 或 PureComponent),然后讓這個新的組件類去渲染參數(shù)組件,這樣就可以對參數(shù)組件實現(xiàn)包裝或代理 props 的操作。繼承型高階組件也是返回一個新的組件類,但這個新的類不繼承根組件類,而是繼承于參數(shù)組件類,這樣我們就可以重寫參數(shù)組件類中的一些方法。
這里的高階組件應(yīng)該叫做高階組件生成函數(shù),其執(zhí)行的返回值才叫高階組件,為了方便我們這里統(tǒng)一稱為高階組件了,請知悉。
下面分別來說下這兩類的高階組件。

代理型高階組件

代理型高階組件接受一個組件作為參數(shù),返回一個新的繼承于根組件(Component、PureComponent)的組件,并用該新組件渲染參數(shù)組件,其基本結(jié)構(gòu)為:

export const HOC = (SomeComponent) => {
    return class HOCComponent extends PureComponent{
        render(){
            doSomeThing()...
            return(
                <SomeComponent />
            );
        }
    }
}

代理型高階組件有以下幾個作用:

  • 代理 props
  • 訪問 ref
  • 抽取狀態(tài)
  • 包裝組件

代理 props

代理型高階組件可以接收被包裝組件的 props,并對這些 props 進行操作,如增刪改操作。在渲染被包裝組件時,傳入被修改的 props,如本文開頭的例子。
基本結(jié)構(gòu)如下:

export const HOC = (SomeComponent) => {
    return class HOCComponent extends PureComponent{
        render(){
            const newProps = doSomeThing(this.props);
            return(
                <SomeComponent {...newProps} />
            );
        }
    }
}

訪問 ref

ref 是一個特殊的屬性,可以聲明為函數(shù),如果 ref 屬性是一個函數(shù),那么將在組件裝載完成后自動執(zhí)行該函數(shù),并將當(dāng)前組件的實例傳入該函數(shù)中
看一個栗子:

class SubComponent extends PureComponent{
    render(){
        return(
            <div className = "box">
                我想要被獲取!!!
            </div>
        );
    }
}

const HOC = (InnerComponent) => {
    return class HOCComponent extends PureComponent{
        constructor(...args){
            super(...args);
            this.handSubRef = this.handSubRef.bind(this);
        }
        
        // 獲取子組件的實例
        // 該函數(shù)在子組件被裝載后自動調(diào)用
        handSubRef(subEle){
            console.log(subEle);
        }

        render(){
            return(
                <InnerComponent ref = { this.handSubRef } />
            );
        }
    }
}

export default HOC(SubComponent);

這樣,在參數(shù)組件被裝載后,就會自動執(zhí)行 handSubRef 函數(shù),并將參數(shù)組件的實例傳入 handSubRef 函數(shù)。在 handSubRef 中可以獲取參數(shù)組件的一些列屬性,如 state、props 等。
注意,如果參數(shù)組件是一個無狀態(tài)組件,那么傳入 handSubRef 的參數(shù)將是 null

const SubComponent = (props) => {
    return(
        <div className = "box">
            我想要被獲取!!!
        </div>
    );
} 

const HOC = (InnerComponent) => {
    return class HOCComponent extends PureComponent{
        constructor(...args){
            super(...args);
            this.handSubRef = this.handSubRef.bind(this);
        }
        
        // 參數(shù)組件是無狀態(tài)組件
        // subEle 為 null
        handSubRef(subEle){
            console.log(subEle);
        }

        render(){
            return(
                <InnerComponent ref = { this.handSubRef } />
            );
        }
    }
}

export default HOC(SubComponent);

抽取狀態(tài)

當(dāng)一個組件的功能較復(fù)雜時,我們建議將組件拆分為容器組件和UI組件,UI組件負(fù)責(zé)展示,容器組件用來進行邏輯管理。這個步驟就是“抽取狀態(tài)”。
我們將前面的計算器應(yīng)用中的組件進行一些修改:

...
const UICounter = (props) => {
    const { increase,decrease,value} = props;
    return(
        <div className = "counter">
            <div>
                <button onClick = { increase }>+</button>
                <button onClick = { decrease }>-</button>
            </div>
            <span>當(dāng)前的值為:{ value }</span>
        </div>
    );
}

const HOC = (SubEle) => {
    return class HOCComponent extends PureComponent{
        constructor(props) {
            super(props);
            // 獲取初始狀態(tài)
            this.state = {
                value:store.getState().value,
            };
        }

        componentWillMount() {
            // 監(jiān)聽 store 變化
            store.subscribe(this.watchStore.bind(this));
        }

        componentWillUnmount() {
            // 對 store 變化取消監(jiān)聽
            store.unsubscribe(this.watchStore.bind(this));
        }

        // 監(jiān)聽回調(diào)函數(shù),當(dāng) store 變化后執(zhí)行
        watchStore(){
            // 回調(diào)函數(shù)中重新設(shè)置狀態(tài)
            this.setState(this.getCurrentState());
        }

        // 從 store 中獲取狀態(tài)
        getCurrentState(){
            return{
                value:store.getState().value,
            }
        }

        // 增加函數(shù)
        increase(){
            // 派發(fā) INCREMENT Action
            store.dispatch(ACTIONS.increament());
        }

        // 減少函數(shù)
        decrease(){
            // 派發(fā) DECREAMENT Action
            store.dispatch(ACTIONS.decreament());
        }

        render(){
            return(
                <UICounter
                    increase = { this.increase.bind(this)}
                    decrease = { this.decrease.bind(this)}
                    value = { this.state.value }
                />
            );
        }
    }
}

export default HOC(UICounter);
...

包裝組件

包裝組件就是對某些組件進行組合,返回不同的組合形式,同時可以設(shè)置展示樣式。如將展示性 select 和輸入表單包裝成可搜索的 select等。
基本結(jié)構(gòu)如下:

// 可以傳入一個數(shù)組,或者單個組件
const HOC = (SubComponents) => {
    return class HOCComponent extends PureComponent{
        render(){
            const style = ...
            // 將子組件組合成一個大組件,同時可以修改樣式
            return(
                <div style = { style }>
                    {...SubComponents}
                </div>
            );
        }
    }
}

export default HOC([Select,SearchBox]);

至此,代理型高階組件就說完了,下面說說繼承型高階組件。

繼承型高階組件

和代理型高階組件創(chuàng)建一個繼承于根組件(Component、PureComponent)的組件類不同的是,繼承型高階組件是創(chuàng)建一個繼承于參數(shù)組件的組件類,以此可以覆寫父組件中的一些方法。
最常用的應(yīng)用是重寫父組件的生命周期函數(shù):


const HOC = (ParComponents) => {
    return class HOCComponent extends ParComponents{
        // 覆寫父組件的生命周期函數(shù)
        shouldComponentUpdate(nextProps, nextState) {
            ...
        }

        render(){
            // 渲染時仍然按照父組件的 render 函數(shù)進行渲染
            return super.render();
        }
    }
}

export default HOC(ParComponents);

子組件覆寫了父組件的生命周期方法,以獲取更好的渲染性能,在渲染(render)時,還是調(diào)用父類的 render 方法。

函數(shù)作為子組件

前面用高階組件實現(xiàn)了代理 props 功能,對于特定的組件,需要傳入特定的 props 名,由于每個組件需要的 props 可能有差異,如果使用高階組件對參數(shù)組件進行判斷,再傳入合適的 props 名顯然太麻煩了。這時如果我們需要更通用的方案,可以接收函數(shù)作為子組件,利用函數(shù)的形參的特性來排除組件 props 名之間的差異性。看一下演示代碼:

...
// 此組件需要使用 user props
const SubComponent1 = (user) => {
    return(
        <TestComponent user = { user } />
    );
} 

// 此組件需要使用 username props
const SubComponent2 = (user) => {
    return(
        <TestComponent2 username = { user } />
    );
} 

// 高階組件接受 testUser 作為 props
const HOC = (props) => {
    const { testUser } = props;
    return(
        props.children(testUser)
    );
}
...

使用此高階組件傳遞 props:

...
<HOC testUser = "MIKE">
    { SubComponent1 }
</HOC>

<HOC testUser = "JACK">
    { SubComponent2 }
</HOC>
...

高階組件接收一個函數(shù)作為子組件,然后調(diào)用這個組件函數(shù),并傳入相應(yīng)的 props,函數(shù)調(diào)用的結(jié)果返回一個組件,組件需要接收什么樣的 props 名可以自行定義,不用在高階組件中進行判斷了。
以函數(shù)作為組件的形式主要用于不同的組件需要接收同一份 props,但是它們需要的 props 名稱不一樣的情況,可以用函數(shù)的形參巧妙的化解掉父組件的判斷。雖然用途較少,但不失為一個優(yōu)秀的模式,將 children 進行調(diào)用簡直是神來之筆有木有。

總結(jié)

本文談到了 React 中的高階組件,高階組件主要包括兩種類型:

  • 代理型高階組件
  • 繼承型高階組件

代理型高階組件的主要用途是:

  • 代理子組件的 props(對 props 進行增刪改操作)
  • 獲取子組件的 ref(實例)
  • 抽取子組件狀態(tài)(拆分邏輯)
  • 將子組件進行包裝(組合子組件,調(diào)整樣式等)

繼承型高階組件的主要用途是覆寫父組件的生命周期方法,通常是 shouldComponentUpdate 方法,以此來提高渲染性能,渲染時調(diào)用父類的 render 函數(shù)(super.render())。
最后,介紹了函數(shù)作為子組件的情形,主要用于不同的組件接受同一份 props,但是它們各自需要的 props 名不同的情況,利用函數(shù)的形參巧妙化解父組件的額外判斷操作。這是一種優(yōu)秀的模式,讓人耳目一新。

完。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 在目前的前端社區(qū),『推崇組合,不推薦繼承(prefer composition than inheritance)...
    Wenliang閱讀 77,716評論 16 124
  • 什么是高階組件? high-order-function(高階函數(shù))相信大多數(shù)開發(fā)者來說都熟悉,即接受函數(shù)作為參數(shù)...
    哇塞田閱讀 9,127評論 9 23
  • React高階組件探究 在使用React構(gòu)建項目的過程中,經(jīng)常會碰到在不同的組件中需要用到相同功能的情況。不過我們...
    緋色流火閱讀 2,580評論 4 19
  • 提到高階組件,不由得想起了函數(shù)式編程的高階函數(shù),高階函數(shù)就是指:接受函數(shù)作為輸入,或者輸出一個函數(shù)。例如map,s...
    Dabao123閱讀 1,630評論 0 1
  • “ 這里的書種類不少,你想買那一類的呢?”在介紹書之前,他必須要明確來的人想買那一類的書,他才肯細(xì)致的介紹。 在大...
    懷曇憶蝶閱讀 170評論 9 2