大佬封裝React Context Composer的詳細步驟(分享)

藏色散人
發布: 2021-12-20 13:57:25
轉載
2141 人瀏覽過

本文由composer教學欄位來介紹大佬是如何一步步封裝一個React Context Composer,希望對需要的朋友有幫助!

我是如何一步步封裝一個React Context Composer?

動機

React的狀態管理方案有很多,像是Redux、Mobx、Recoil等,目前我只體驗過Redux,覺得還是比較笨重一點。因為平常寫Hooks比較多,所以我比較傾向使用Context Provider配合useContext這個hook來做,這樣也易於狀態的拆分與組合。這裡,我們不討論各家狀態管理方案的優劣,將目光聚焦在使用Context時遇到的一個多層嵌套的問題。

下圖,是我最近在寫的一個taro react hooks ts專案抽離出來的一些程式碼。我對一些全域狀態進行了拆分(拆分的目的是為了減少不必要的重新渲染),然後再把它們嵌套起來。這種寫法讓我回想起了曾經被回調地獄支配的感覺,很難受。因此,我想到了自己去封一個高階組件,從寫法上把結構「扁平化」。

<LoadingContext.Provider value={{ loading, setLoading }}>
  <UserDataContext.Provider value={{ name: "ascodelife", age: 25 }}>
    <ThemeContext.Provider value={"light"}>
    {/* ....more Providers as long as you want */}
    </ThemeContext.Provider>
  </UserDataContext.Provider>
</LoadingContext.Provider>
登入後複製

最容易得到的方案

這裡,我很快的就寫出了第一個方案,借助reduceRight去完成Provider的巢狀。

這裡用reduceRight而不用reduce的原因是,我們更習慣從外層到內層的書寫順序。

// ContextComposer.tsx
import React from &#39;react&#39;;
type IContextComposerProps = {
  contexts: { context: React.Context<any>; value: any }[];
};
const ContextComposer: React.FC<IContextComposerProps> = ({ contexts, children }) => {
  return (
    <>
      {contexts.reduceRight((child, parent) => {
        const { context, value } = parent;
        return <context.Provider value={value}>{child}</context.Provider>;
      }, children)}
    </>
  );
};
export default ContextComposer;
// App.tsx
<ContextComposer
  contexts={[
    { context: ThemeContext, value: "light" },
    { context: UserDataContext, value: { name: "ascodelife", age: 25 } },
    { context: LoadingContext, value: { loading, setLoading } },
  ]}>
    { children }
</ContextComposer>
登入後複製

實際體驗後發現,雖然說能用是能用,但是開發體驗差那麼一點。它的問題在於,元件入參時傳的value是any類型,這意味著放棄了ts的靜態型別檢查。在傳參時,由於不會對value做靜態類型檢查,敲起程式碼來不僅不會有任何程式碼提示,也有可能造成一些比較低階的執行時間錯誤。差評!

基於React.cloneElement()的改造方案

為了改造上面的這個方案,我翻到了一個比較冷門但好用的函數- React. cloneElement()。這個函數沒有很多需要值得注意的點,主要看一眼它的三個入參,第一個是parent element,第二個是parent props,第三個是剩餘參數...children,除第一個參數外面,其他都是可選值。

舉個例子:

<!-- 调用函数 -->
React.cloneElement(<div/>,{},<span/>);
<!-- 相当于创建了这样一个结构 -->
<div> 
    <span></span>
</div>
登入後複製

那麼下面開始改造,reduceRight的架子不動,改一下入參的類型和reduceRight的回調。

// ContextComposer.tsx
import React from &#39;react&#39;;
type IContextComposerProps = {
  contexts: React.ReactElement[];
};
const ContextComposer: React.FC<IContextComposerProps> = ({ contexts, children }) => {
  return (
    <>
      {contexts.reduceRight((child, parent) => {
        return React.cloneElement(parent,{},child);
      }, children)}
    </>
  );
};
export default ContextComposer;
// App.tsx
<ContextComposer
  contexts={[
      <ThemeContext.Provider value={"light"} />,
      <UserDataContext.Provider value={{ name: "ascodelife", age: 25 }} />,
      <LoadingContext.Provider value={{ loading, setLoading }} />,
  ]}>
    { children }
</ContextComposer>
登入後複製

經過改造後,我們在傳參時就好像是真的在創建一個元件(當然實際上也創建了元件,只是這個元件本身沒有被渲染到虛擬Dom上,實際渲染上去的是被克隆後的副本)。同時,我們剛才關注的value的靜態類型檢查問題也得到了解決。

tips: React.cloneElement(parent,{},child)等價於React.cloneElement(parent,{children:child}),你知道為什麼嗎?

相關資源

原始碼已經同步到了github(https://github.com/ascodelife/react-context-provider-composer)。

同時也打包到了npm倉庫(https://www.npmjs.com/package/@ascodelife/react-context-provider-composer),歡迎體驗。

以上是大佬封裝React Context Composer的詳細步驟(分享)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:juejin.im
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板