react高階組件

此篇文章準(zhǔn)備從高階組件的概念出發(fā),通過應(yīng)用場景結(jié)合例子來學(xué)習(xí)react的高階組件。

何為高階組件

在開發(fā) React 組件過程中,很容易發(fā)現(xiàn)這樣一種現(xiàn)象,某些功能是多個(gè)組件通用的,而最直接的想法就是要把這部分共用邏輯提取出來重用。當(dāng)然,這里有個(gè)現(xiàn)成的思路,那就是將這些邏輯寫成React 組件來進(jìn)行復(fù)用。不過,有些情況下,這些共用邏輯單獨(dú)無法使用,它們只是對(duì)其他組件的功能加強(qiáng)。
這時(shí)候,我們就可以利用“高階組件(HoC)”這種組件設(shè)計(jì)模式來解決問題。


高階組件的基本形式

“高階組件”雖然名為“組件”,其實(shí)是一個(gè)函數(shù),只不過這個(gè)函數(shù)比較特殊,它接受至少一個(gè) React 組件為參數(shù),并且能夠返回一個(gè)全新的 React 組件作為結(jié)果,換句話說說,這個(gè)新產(chǎn)生的 React 組件是對(duì)作為參數(shù)的組件的包裝。
一個(gè)最簡單的高階組件是這樣:

const withDoNothing = (Component) => {
  const NewComponent = (props) => {
    return <Component {...props} />;
  };
  return NewComponent;
};

上面的函數(shù)withDoNothing就是一個(gè)高階組件,作為一項(xiàng)業(yè)界通用的代碼規(guī)范,高階組件的命名一般都帶with前綴,命名中后面的部分代表這個(gè)高階組件的功能。
對(duì)于上面這個(gè)高階組件的例子我們能夠總結(jié)出高階組件的基本套路:

  1. 高階組件不能去修改作為參數(shù)的組件,高階組件必須是一個(gè)純函數(shù),不應(yīng)該有任何副作用。
  2. 高階組件返回的結(jié)果必須是一個(gè)新的 React 組件,這個(gè)新的組件的 JSX 部分肯定會(huì)包含作為參數(shù)的組件。
  3. 高階組件一般需要把傳給自己的 props 轉(zhuǎn)手傳遞給作為參數(shù)的組件。

利用高階組件提取公共邏輯

這里有一個(gè)這樣的場景:對(duì)于一個(gè)電商類網(wǎng)站,“退出登錄”按鈕、“購物車”這些模塊,就只有用戶登錄之后才顯示。
實(shí)現(xiàn)上述的邏輯,“退出登錄按鈕“會(huì)這么寫:

//假設(shè)有一個(gè)函數(shù) getUserId 能夠從 cookies 中讀取登錄用戶的 ID,如果用戶未登錄,這個(gè) getUserId 就返回空
const LogoutButton = () => {
  if (getUserId()) {
    return ...; // 顯示”退出登錄“的JSX
  } else {
    return null;
  }
}

同樣,“購物車”的代碼就是這樣:

const ShoppingCart = () => {
  if (getUserId()) {
    return ...; // 顯示”購物車“的JSX
  } else {
    return null;
  }
};

上面的兩個(gè)組件很明顯有相同的邏輯,這個(gè)時(shí)候就可以發(fā)揮高階組件的作用了。我們定義一個(gè)withLogin的高階組件,其內(nèi)部實(shí)現(xiàn)代碼如下:

const withLogin = (Component) => {
  const NewComponent = (props) => {
    if (getUserId()) {
      return <Component {...props} />;
    } else {
      return null;
    }
  }

  return NewComponent;
};

于是,“購物車”和“退出按鈕”的模塊就可以變成這樣:

const LogoutButton = withLogin((props) => {
  return ...; // 顯示”退出登錄“的JSX
});

const ShoppingCart = withLogin(() => {
  return ...; // 顯示”購物車“的JSX
});

這樣一來,我們只需要關(guān)注各自內(nèi)部實(shí)現(xiàn)就ok了。


高階組件的進(jìn)階

1:接受多個(gè)組件參量

還是前面的例子,我們來改進(jìn)上面的 withLogin,定義為withLoginAndLogout,讓它接受兩個(gè) React 組件,根據(jù)用戶是否登錄選擇渲染合適的組件。

const withLoginAndLogout = (ComponentForLogin, ComponentForLogout) => {
  const NewComponent = (props) => {
    if (getUserId()) {
      return <ComponentForLogin {...props} />;
    } else {
      return <ComponentForLogout{...props} />;
    }
  }
  return NewComponent;
};

這樣一來,我們就可以產(chǎn)生根據(jù)用戶登錄狀態(tài)顯示不同的內(nèi)容

const TopButtons = withLoginAndLogout(
  LogoutButton,
  LoginButton
);

2:鏈?zhǔn)秸{(diào)用高階組件

高階組件的鏈?zhǔn)秸{(diào)用賦予了高階組件新的生命。
假設(shè),有三個(gè)高階組件分別是 withOne、withTwo 和 withThree,那么,如果要賦予一個(gè)組件 X某些高階組件的超能力,那么,你要做的就是挨個(gè)使用高階組件包裝,代碼如下:

const X1 = withOne(X);
const X2 = withTwo(X1);
const X3 = withThree(X2);
const SuperX = X3; //最終的SuperX具備三個(gè)高階組件的超能力

當(dāng)然,我們可以簡化直接連續(xù)調(diào)用高階組件,如下:

const SuperX = withThree(withTwo(withOne(X)));

對(duì)于 X 而言,它被高階組件包裝了,至于被一個(gè)高階組件包裝,還是被 N 個(gè)高階組件包裝,沒有什么差別。
那么我們是不是可以往更深的角度去思考?
因?yàn)楦唠A組件本身就是一個(gè)純函數(shù),純函數(shù)是可以組合使用的,所以,我們其實(shí)可以把多個(gè)高階組件組合為一個(gè)高階組件,然后用這個(gè)組合高階組件去包裝X。
在上面代碼中使用的compose,是函數(shù)式編程中應(yīng)用的一種方法,作用就是把多個(gè)函數(shù)組合為一個(gè)函數(shù),所以一個(gè)應(yīng)用中多個(gè)組件都需要多個(gè)高階組件包裝,那就可以用compose組合這些高階組件為一個(gè)高階組件,這樣在使用多個(gè)高階組件的地方實(shí)際上就只需要使用一個(gè)高階組件了。這里提供一個(gè)實(shí)現(xiàn)compose的方法作為參考。

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

reduce的語法解釋。


高階組件的限制

所有的事物都有兩面性,看待高階組件的時(shí)候也是需要辨證得使用。
1:高階組件不得不處理 displayName,不然debug 會(huì)很痛苦。當(dāng) React 渲染出錯(cuò)的時(shí)候,靠組件的 displayName 靜態(tài)屬性來判斷出錯(cuò)的組件類,而高階組件總是創(chuàng)造一個(gè)新的 React 組件類,所以,每個(gè)高階組件都需要處理一下 displayName。
比如:

const withExample = (Component) => {
  const NewComponent = (props) => {
    return <Component {...props} />;
  }
  
  NewComponent.displayName = `withExample(${Component.displayName || Component.name || 'Component'})`;
  
  return NewCompoennt;
};

2:對(duì)于 React 生命周期函數(shù),高階組件不用怎么特殊處理,但是,如果內(nèi)層組件包含定制的靜態(tài)函數(shù),這些靜態(tài)函數(shù)的調(diào)用在 React 生命周期之外,那么高階組件就必須要在新產(chǎn)生的組件中增加這些靜態(tài)函數(shù)的支持,這更加麻煩。
3:避免重復(fù)產(chǎn)生 React 組件
不要這樣使用高階組件

const Example = () => {
  const EnhancedFoo = withExample(Foo);
  return <EnhancedFoo />
}

如果像上述使用,每一次渲染 Example,都會(huì)用高階組件產(chǎn)生一個(gè)新的組件,雖然都叫做 EnhancedFoo,但是對(duì) React 來說是一個(gè)全新的東西,在重新渲染的時(shí)候不會(huì)重用之前的虛擬 DOM,會(huì)造成極大的浪費(fèi)。
正確的寫法是下面這樣:

const EnhancedFoo = withExample(Foo);

const Example = () => {
  return <EnhancedFoo />
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容