摘要:今天我們就來解讀一下的源碼。比較有意思,將定時器以方式提供出來,并且提供了方法。實現方式是,在組件內部維護一個定時器,實現了組件更新銷毀時的計時器更新銷毀操作,可以認為這種定時器的生命周期綁定了組件的生命周期,不用擔心銷毀和更新的問題。
1. 引言
React PowerPlug 是利用 render props 進行更好狀態管理的工具庫。
React 項目中,一般一個文件就是一個類,狀態最細粒度就是文件的粒度。然而文件粒度并非狀態管理最合適的粒度,所以有了 Redux 之類的全局狀態庫。
同樣,文件粒度也并非狀態管理的最細粒度,更細的粒度或許更合適,因此有了 React PowerPlug。
比如你會在項目中看到這種眼花繚亂的 state:
class App extends React.PureComponent { state = { name = 1 isLoading = false isFetchUser = false data = {} disableInput = false validate = false monacoInputValue = "" value = "" } render () { /**/ } }
其實真正 App 級別的狀態并沒有那么多,很多 諸如受控組件 onChange 臨時保存的無意義 Value 找不到合適的地方存儲。
這時候可以用 Value 管理局部狀態:
{({ value, set, reset }) => ( <> > )}
可以看到,這個問題本質上應該拆成新的 React 類解決,但這也許會導致項目結構更混亂,因此 RenderProps 還是必不可少的。
今天我們就來解讀一下 React PowerPlug 的源碼。
2. 精讀 2.1. Value這是一個值操作的工具,功能與 Hooks 中 useState 類似,不過多了一個 reset 功能(Hooks 其實也未嘗不能有,但 Hooks 確實沒有 Reset)。
用法源碼{({ value, set, reset }) => ( <> > )}
源碼地址
原料:無
State 只存儲一個屬性 value,并賦初始值為 initial:
export default { state = { value: this.props.initial }; }
方法有 set reset。
set 回調函數觸發后調用 setState 更新 value。
reset 就是調用 set 并傳入 this.props.initial 即可。
2.2. ToggleToggle 是最直接利用 Value 即可實現的功能,因此放在 Value 之后說。Toggle 值是 boolean 類型,特別適合配合 Switch 等組件。
既然 Toggle 功能弱于 Value,為什么不用 Value 替代 Toggle 呢?這是個好問題,如果你不擔心自己代碼可讀性的話,的確可以永遠不用 Toggle。用法
源碼{({ on, toggle }) => }
源碼地址
原料:Value
核心就是利用 Value 組件,value 重命名為 on,增加了 toggle 方法,繼承 set reset 方法:
export default { toggle: () => set(on => !on); }
理所因當,將 value 值限定在 boolean 范圍內。
2.3. Counter與 Toggle 類似,這也是繼承了 Value 就可以實現的功能,計數器。
用法源碼{({ count, inc, dec }) => ( )}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 count,增加了 inc dec incBy decBy 方法,繼承 set reset 方法。
與 Toggle 類似,Counter 將 value 限定在了數字,那么比如 inc 就會這么實現:
export default { inc: () => set(value => value + 1); }
這里用到了 Value 組件 set 函數的多態用法。一般 set 的參數是一個值,但也可以是一個函數,回調是當前的值,這里返回一個 +1 的新值。
2.4. List操作數組。
用法源碼{({ list, pull, push }) => (
)}{list.map({ tag }) => ( pull(value => value === tag)}> {tag} )}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 list,增加了 first last push pull sort 方法,繼承 set reset 方法。
export default { list: value, first: () => value[0], last: () => value[Math.max(0, value.length - 1)], set: list => set(list), push: (...values) => set(list => [...list, ...values]), pull: predicate => set(list => list.filter(complement(predicate))), sort: compareFn => set(list => [...list].sort(compareFn)), reset };
為了利用 React Immutable 更新的特性,因此將 sort 函數由 Mutable 修正為 Immutable,push pull 同理。
2.5. Set存儲數組對象,可以添加和刪除元素。類似 ES6 Set。和 List 相比少了許多功能函數,因此只承擔添加、刪除元素的簡單功能。
用法需要注意的是,initial 是數組,而不是 Set 對象。
源碼{({ values, remove, add }) => ( )} {values.map(tag => ( remove(tag)}>{tag} ))}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 values 且初始值為 [],增加了 add remove clear has 方法,保留 reset 方法。
實現依然很簡單,add remove clear 都利用 Value 提供的 set 進行賦值,只要實現幾個操作數組方法即可:
const unique = arr => arr.filter((d, i) => arr.indexOf(d) === i); const hasItem = (arr, item) => arr.indexOf(item) !== -1; const removeItem = (arr, item) => hasItem(arr, item) ? arr.filter(d => d !== item) : arr; const addUnique = (arr, item) => (hasItem(arr, item) ? arr : [...arr, item]);
has 方法則直接復用 hasItem。核心還是利用 Value 的 set 函數一招通吃,將操作目標鎖定為數組類型罷了。
2.6. mapMap 的實現與 Set 很像,類似 ES6 的 Map。
用法與 Set 不同,Map 允許設置 Key 名。需要注意的是,initial 是對象,而不是 Map 對象。
源碼源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 values 且初始值為 {},增加了 set get clear has delete 方法,保留 reset 方法。
由于使用對象存儲數據結構,操作起來比數組方便太多,已經不需要再解釋了。
值得吐槽的是,作者使用了 != 判斷 has:
export default { has: key => values[key] != null; }
這種代碼并不值得提倡,首先是不應該使用二元運算符,其次比較推薦寫成 values[key] !== undefined,畢竟 set("null", null) 也應該算有值。
2.7. stateState 純粹為了替代 React setState 概念,其本質就是換了名字的 Value 組件。
用法值得注意的是,setState 支持函數和值作為參數,是 Value 組件本身支持的,State 組件額外適配了 setState 的另一個特性:合并對象。
{({ state, setState }) => { const onStart = data => setState({ loading: true }); const onFinish = data => setState({ data, loading: false }); return ( ); }}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 state 且初始值為 {},增加了 setState 方法,保留 reset 方法。
setState 實現了合并對象的功能,也就是傳入一個對象,并不會覆蓋原始值,而是與原始值做 Merge:
export default { setState: (updater, cb) => set( prev => ({ ...prev, ...(typeof updater === "function" ? updater(prev) : updater) }), cb ); }2.8. Active
這是一個內置鼠標交互監聽的容器,監聽了 onMouseUp 與 onMouseDown,并依此判斷 active 狀態。
用法源碼{({ active, bind }) => ( You are {active ? "clicking" : "not clicking"} this div.)}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 active 且初始值為 false,增加了 bind 方法。
bind 方法也巧妙利用了 Value 提供的 set 更新狀態:
export default { bind: { onMouseDown: () => set(true), onMouseUp: () => set(false) } };2.9. Focus
與 Active 類似,Focus 是當 focus 時才觸發狀態變化。
用法源碼{({ focused, bind }) => ( )}You are {focused ? "focusing" : "not focusing"} the input.
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 focused 且初始值為 false,增加了 bind 方法。
bind 方法與 Active 如出一轍,僅是監聽時機變成了 onFocus 和 onBlur。
2.10. FocusManager不知道出于什么考慮,FocusManager 的官方文檔是空的,而且 Help wanted。。
正如名字描述的,這是一個 Focus 控制器,你可以直接調用 blur 來取消焦點。
用法筆者給了一個例子,在 5 秒后自動失去焦點:
源碼{({ focused, blur, bind }) => ( { setTimeout(() => { blur(); }, 5000); }} />)}You are {focused ? "focusing" : "not focusing"} the input.
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 focused 且初始值為 false,增加了 bind blur 方法。
blur 方法直接調用 document.activeElement.blur() 來觸發其 bind 監聽的 onBlur 達到更新狀態的效果。
By the way, 還監聽了 onMouseDown 與 onMouseUp:
export default { bind: { tabIndex: -1, onBlur: () => { if (canBlur) { set(false); } }, onFocus: () => set(true), onMouseDown: () => (canBlur = false), onMouseUp: () => (canBlur = true) } };
可能意圖是防止在 mouseDown 時觸發 blur,因為 focus 的時機一般是 mouseDown。
2.11. Hover與 Focus 類似,只是觸發時機為 Hover。
用法源碼{({ hovered, bind }) => ( You are {hovered ? "hovering" : "not hovering"} this div.)}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 hovered 且初始值為 false,增加了 bind 方法。
bind 方法與 Active、Focus 如出一轍,僅是監聽時機變成了 onMouseEnter 和 onMouseLeave。
2.12. Touch與 Hover 類似,只是觸發時機為 Hover。
用法源碼{({ touched, bind }) => ( You are {touched ? "touching" : "not touching"} this div.)}
源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 touched 且初始值為 false,增加了 bind 方法。
bind 方法與 Active、Focus、Hover 如出一轍,僅是監聽時機變成了 onTouchStart 和 onTouchEnd。
2.13. Field與 Value 組件唯一的區別,就是
用法這個用法和 Value 沒區別:
{({ value, set }) => ( set(e.target.value)} /> )}
但是用 bind 更簡單:
源碼{({ bind }) => }
源碼地址
原料:Value
依然利用 Value 組件,value 保留不變,初始值為 "",增加了 bind 方法,保留 set reset 方法。
與 Value 的唯一區別是,支持了 bind 并封裝 onChange 監聽,與賦值受控屬性 value。
export default { bind: { value, onChange: event => { if (isObject(event) && isObject(event.target)) { set(event.target.value); } else { set(event); } } } };2.14. Form
這是一個表單工具,有點類似 Antd 的 Form 組件。
用法 )} 源碼源碼地址
原料:Value
依然利用 Value 組件,value 重命名為 values 且初始值為 {},增加了 setValues field 方法,保留 reset 方法。
表單最重要的就是 field 函數,為表單的每一個控件做綁定,同時設置一個表單唯一 key:
export default { field: id => { const value = values[id]; const setValue = updater => typeof updater === "function" ? set(prev => ({ ...prev, [id]: updater(prev[id]) })) : set({ ...values, [id]: updater }); return { value, set: setValue, bind: { value, onChange: event => { if (isObject(event) && isObject(event.target)) { setValue(event.target.value); } else { setValue(event); } } } }; } };
可以看到,為表單的每一項綁定的內容與 Field 組件一樣,只是 Form 組件的行為是批量的。
2.15. IntervalInterval 比較有意思,將定時器以 JSX 方式提供出來,并且提供了 stop resume 方法。
用法源碼{({ start, stop }) => ( <> The time is now {new Date().toLocaleTimeString()}> )}
源碼地址
原料:無
提供了 start stop toggle 方法。
實現方式是,在組件內部維護一個 Interval 定時器,實現了組件更新、銷毀時的計時器更新、銷毀操作,可以認為這種定時器的生命周期綁定了 React 組件的生命周期,不用擔心銷毀和更新的問題。
具體邏輯就不列舉了,利用 setInterval clearInterval 函數基本上就可以了。
2.16. ComposeCompose 也是個有趣的組件,可以將上面提到的任意多個組件組合使用。
用法源碼{(counter, toggle) => ( )}
源碼地址
原料:無
通過遞歸渲染出嵌套結構,并將每一層結構輸出的值存儲到 propsList 中,最后一起傳遞給組件。這也是為什么每個函數 value 一般都要重命名的原因。
在 精讀《Epitath 源碼 - renderProps 新用法》 文章中,筆者就介紹了利用 generator 解決高階組件嵌套的問題。
在 精讀《React Hooks》 文章中,介紹了 React Hooks 已經實現了這個特性。
所以當你了解了這三種 "compose" 方法后,就可以在合適的場景使用合適的 compose 方式簡化代碼。
3. 總結看完了源碼分析,不知道你是更感興趣使用這個庫呢,還是已經躍躍欲試開始造輪子了呢?不論如何,這個庫的思想在日常的業務開發中都應該大量實踐。
另外 Hooks 版的 PowerPlug 已經 4 個月沒有更新了(非官方):react-powerhooks,也許下一個維護者/貢獻者 就是你。
討論地址是:精讀《React PowerPlug》 · Issue #129 · dt-fe/weekly
如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀 - 幫你篩選靠譜的內容。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102068.html
摘要:精讀源碼一共行,我們分析一下其精妙的方式。更多討論討論地址是精讀新用法如果你想參與討論,請點擊這里,每周都有新的主題,周末或周一發布。前端精讀幫你篩選靠譜的內容。 1 引言 很高興這一期的話題是由 epitath 的作者 grsabreu 提供的。 前端發展了 20 多年,隨著發展中國家越來越多的互聯網從業者涌入,現在前端知識玲瑯滿足,概念、庫也越來越多。雖然內容越來越多,但作為個體的...
摘要:可以看到,這樣不僅沒有占用組件自己的,也不需要手寫回調函數進行處理,這些處理都壓縮成了一行。效果通過拿到周期才執行的回調函數。實現等價于的回調僅執行一次時,因此直接把回調函數拋出來即可。 1 引言 上周的 精讀《React Hooks》 已經實現了對 React Hooks 的基本認知,也許你也看了 React Hooks 基本實現剖析(就是數組),但理解實現原理就可以用好了嗎?學的是...
摘要:會自動觸發函數內回調函數的執行。因此利用并將依賴置為使代碼在所有渲染周期內,只在初始化執行一次。同時代碼里還對等公共方法進行了包裝,讓這些回調函數中自帶效果。前端精讀幫你篩選靠譜的內容。 1. 引言 react-easy-state 是個比較有趣的庫,利用 Proxy 創建了一個非常易用的全局數據流管理方式。 import React from react; import { stor...
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現了Vue-router,react-router-dom等前端路由管理庫,利用他們構建出來的單頁面應用,也是越來越接近原生的體驗,再也不是以前的點擊標簽跳轉頁面,刷新整個頁面了,那么他們的原理是什么呢? 優質gitHub開源練手項目: ...
閱讀 2026·2021-08-21 14:09
閱讀 486·2019-08-30 15:44
閱讀 2114·2019-08-29 16:32
閱讀 1377·2019-08-29 15:36
閱讀 3443·2019-08-29 12:43
閱讀 2785·2019-08-29 11:14
閱讀 436·2019-08-28 18:26
閱讀 2253·2019-08-26 13:57