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

本文由composer教程欄目給大家介紹大佬是如何一步步封裝一個react context composer,希望對需要的朋友有所幫助!

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

動機

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

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

<loadingcontext.provider> ??<userdatacontext.provider> ????<themecontext.provider> ????{/*?....more?Providers?as?long?as?you?want?*/} ????</themecontext.provider> ??</userdatacontext.provider></loadingcontext.provider>

最易得的方案

這里,我很快的就寫出了第一種方案,借助reduceRight去完成Provider的嵌套。

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

//?ContextComposer.tsx import?React?from?'react'; type?IContextComposerProps?=?{ ??contexts:?{?context:?React.Context<any>;?value:?any?}[]; }; const?ContextComposer:?React.FC<icontextcomposerprops>?=?({?contexts,?children?})?=&gt;?{ ??return?( ???? ??????{contexts.reduceRight((child,?parent)?=&gt;?{ ????????const?{?context,?value?}?=?parent; ????????return?<context.provider>{child}</context.provider>; ??????},?children)} ????&gt; ??); }; export?default?ContextComposer; //?App.tsx <contextcomposer> ????{?children?} </contextcomposer></icontextcomposerprops></any>

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

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

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

舉個例子:

<!-- 調用函數 --> React.cloneElement(<div></div>,{},<span></span>); <!-- 相當于創建了這樣一個結構 --> <div>? ????<span></span> </div>

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

//?ContextComposer.tsx import?React?from?'react'; type?IContextComposerProps?=?{ ??contexts:?React.ReactElement[]; }; const?ContextComposer:?React.FC<icontextcomposerprops>?=?({?contexts,?children?})?=&gt;?{ ??return?( ???? ??????{contexts.reduceRight((child,?parent)?=&gt;?{ ????????return?React.cloneElement(parent,{},child); ??????},?children)} ????&gt; ??); }; export?default?ContextComposer; //?App.tsx <contextcomposer></contextcomposer>, ??????<userdatacontext.provider></userdatacontext.provider>, ??????<loadingcontext.provider></loadingcontext.provider>, ??]}&gt; ????{?children?} </icontextcomposerprops>

經過改造后,我們在傳參時就好像是真的在創建一個組件(當然實際上也創建了組件,只是這個組件本身沒有被渲染到虛擬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),歡迎體驗。

? 版權聲明
THE END
喜歡就支持一下吧
點贊11 分享