「不容錯(cuò)過(guò)」手摸手帶你實(shí)現(xiàn) React Hooks
轉(zhuǎn)自手寫 React Hookshttps://juejin.im/post/6872223515580481544
手寫 React Hooks
Hooks 是 React 16.8 新增的特性,它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性 凡是 use 開頭的 React API 都是 Hooks
Hook 是什么
Hook 是一個(gè)特殊的函數(shù),它可以讓你“鉤入” React 的特性。例如,useState 是允許你在 React 函數(shù)組件中添加 state 的 Hook。
為什么使用 Hooks
引用官網(wǎng)描述
在組件之間復(fù)用狀態(tài)邏輯很難
可能要用到 render props (渲染屬性)或者 HOC(高階組件),但無(wú)論是渲染屬性,還是高階組件,都會(huì)在原先的組件外包裹一層父容器(一般都是 div 元素).如果你在 React DevTools 中觀察過(guò) React 應(yīng)用,你會(huì)發(fā)現(xiàn)由 providers,consumers,高階組件,render props 等其他抽象層組成的組件會(huì)形成“嵌套地獄”。
復(fù)雜組件變得難以理解
組件常常在 componentDidMount 和 componentDidUpdate 中獲取數(shù)據(jù)。但是,同一個(gè) componentDidMount 中可能也包含很多其它的邏輯,如設(shè)置事件監(jiān)聽(tīng),而之后需在 componentWillUnmount 中清除。相互關(guān)聯(lián)且需要對(duì)照修改的代碼被進(jìn)行了拆分,而完全不相關(guān)的代碼卻在同一個(gè)方法中組合在一起。如此很容易產(chǎn)生 bug
難以理解的 class
this 指向問(wèn)題:父組件給子組件傳遞函數(shù)時(shí),必須綁定 this
Hook 規(guī)則
只能在函數(shù)內(nèi)部的最外層調(diào)用 Hook,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用 只在 React 函數(shù)中調(diào)用 Hook 在 React 的函數(shù)組件中調(diào)用 Hook 在自定義 Hook 中調(diào)用其他 Hook
利用 eslint 做 hooks 規(guī)則檢查
使用 eslint-plugin-react-hooks 來(lái)檢查代碼錯(cuò)誤
????{
??????"plugins":?["react-hooks"],
??????//?...
??????"rules":?{
????????"react-hooks/rules-of-hooks":?'error',//?檢查?Hook?的規(guī)則
????????"react-hooks/exhaustive-deps":?'warn'?//?檢查?effect?的依賴
??????}
????}
useState
useState 會(huì)返回一個(gè)數(shù)組:一個(gè) state,一個(gè)更新 state 的函數(shù)。
類似 class 組件的 this.setState,但是它不會(huì)把新的 state 和舊的 state 進(jìn)行合并,而是直接替換
????//?保存狀態(tài)的數(shù)組
????let?hookStates?=?[];
????//?索引
????let?hookIndex?=?0;
????
????function?useState(initialState)?{
??????hookStates[hookIndex]?=?hookStates[hookIndex]?||?initialState;
??????//?利用閉包維護(hù)函數(shù)調(diào)用位置
??????let?currentIndex?=?hookIndex;
??????function?setState(newState)?{
????????//?判斷傳入的state是否為函數(shù),如果是把prevState傳入
????????if?(typeof?newState?===?"function")?{
??????????//?重新復(fù)制給newState
??????????newState?=?newState(hookStates[hookIndex]);
????????}
????????//?更新state
????????hookStates[currentIndex]?=?newState;
????????//?觸發(fā)視圖更新
????????render();
??????}
??????//?返回?cái)?shù)組形式,解構(gòu)可寫成任意變量
??????return?[hookStates[hookIndex++],?setState];
????}
useEffect
useEffect 就是一個(gè) Effect Hook,給函數(shù)組件增加了操作副作用的能力。它跟 class 組件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不過(guò)被合并成了一個(gè) API
與 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 調(diào)度的 effect 不會(huì)阻塞瀏覽器更新視圖,這讓你的應(yīng)用看起來(lái)響應(yīng)更快。在特殊情況(例如測(cè)量布局),有單獨(dú)的 useLayoutEffect Hook,使用與 useEffect 相同
????//保存狀態(tài)的數(shù)組
????let?hookStates?=?[];
????//索引
????let?hookIndex?=?0;
????
????function?useEffect(callback,?dependencies)?{
??????if?(hookStates[hookIndex])?{
????????//?非初始調(diào)用
????????let?lastDependencies?=?hookStates[hookIndex];
????????//?判斷傳入依賴項(xiàng)跟上一次是否相同
????????let?same?=?dependencies.every(
??????????(item,?index)?=>?item?===?lastDependencies[index]
????????);
????????if?(same)?{
??????????hookIndex++;
????????}?else?{
??????????hookStates[hookIndex++]?=?dependencies;
??????????callback();
????????}
??????}?else?{
????????//?初始調(diào)用
????????hookStates[hookIndex++]?=?dependencies;
????????callback();
??????}
????}
useMemo
允許你通過(guò)「記住」上一次計(jì)算結(jié)果的方式在多次渲染的之間緩存計(jì)算結(jié)果
使得控制具體子節(jié)點(diǎn)何時(shí)更新變得更容易,減少了對(duì)純組件的需要
????//?保存狀態(tài)的數(shù)組
????let?hookStates?=?[];
????//?索引
????let?hookIndex?=?0;
????
????function?useMemo(factory,?dependencies)?{
??????if?(hookStates[hookIndex])?{
????????//?非首次
????????let?[lastMemo,?lastDependencies]?=?hookStates[hookIndex];
????
????????//?判斷傳入依賴項(xiàng)跟上一次是否相同
????????let?same?=?dependencies.every(
??????????(item,?index)?=>?item?===?lastDependencies[index]
????????);
????????if?(same)?{
??????????hookIndex++;
??????????return?lastMemo;
????????}?else?{
??????????//?只要有一個(gè)依賴變量不一樣的話
??????????let?newMemo?=?factory();
??????????hookStates[hookIndex++]?=?[newMemo,?dependencies];
??????????return?newMemo;
????????}
??????}?else?{
????????//?首次調(diào)用
????????let?newMemo?=?factory();
????????hookStates[hookIndex++]?=?[newMemo,?dependencies];
????????return?newMemo;
??????}
????}
useCallback
允許你在重新渲染之間保持對(duì)相同的回調(diào)引用以使得 shouldComponentUpdate 繼續(xù)工作
????//?保存狀態(tài)的數(shù)組
????let?hookStates?=?[];
????//?索引
????let?hookIndex?=?0;
????
????function?useCallback(callback,?dependencies)?{
??????if?(hookStates[hookIndex])?{
????????//?非首次
????????let?[lastCallback,?lastDependencies]?=?hookStates[hookIndex];
????
????????let?same?=?dependencies.every(
??????????(item,?index)?=>?item?===?lastDependencies[index]
????????);
????????if?(same)?{
??????????hookIndex++;
??????????return?lastCallback;
????????}?else?{
??????????//?只要有一個(gè)依賴變量不一樣的話
??????????hookStates[hookIndex++]?=?[callback,?dependencies];
??????????return?callback;
????????}
??????}?else?{
????????//?首次調(diào)用
????????hookStates[hookIndex++]?=?[callback,?dependencies];
????????return?callback;
??????}
????}
memo
????function?memo(OldFunctionComponent)?{
??????return?class?extends?React.PureComponent?{
????????render()?{
??????????return? ;
????????}
??????};
????}
useContext
接收一個(gè) context 對(duì)象(React.createContext 的返回值)并返回該 context 的當(dāng)前值 useContext(MyContext) 只是讓你能夠讀取 context 的值以及訂閱 context 的變化。仍然需要在上層組件樹中使用
????function?useContext(context)?{
??????return?context._currentValue;
????}
????
????//?父組件
????const?CountCtx?=?React.createContext();
????function?ParentComp()?{
??????const?[state,?setState]?=?React.useState({?number:?0?});
??????return?(
????????
??????????
????????
??????);
????}
????
????//?子組件
????function?Child()?{
??????let?{?state,?setState?}?=?useContext(CountCtx);
??????return?(
????????
??????????{state.number}
??????????
????????
??????);
????}
useRef
useRef 返回一個(gè)可變的 ref 對(duì)象,其 current 屬性被初始化為傳入的參數(shù) useRef 返回的 ref 對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變,也就是說(shuō)每次重新渲染函數(shù)組件時(shí),返回的 ref 對(duì)象都是同一個(gè)(注意使用 React.createRef ,每次重新渲染組件都會(huì)重新創(chuàng)建 ref)
????let?lastRef;
????
????function?useRef(value)?{
??????lastRef?=?lastRef?||?{?current:?value?};
??????return?lastRef;
????}
useReducer
useReducer 和 redux 中 reducer 很像 useState 內(nèi)部就是靠 useReducer 來(lái)實(shí)現(xiàn)的
????//?保存狀態(tài)的數(shù)組
????let?hookStates?=?[];
????//?索引
????let?hookIndex?=?0;
????
????function?useReducer(reducer,?initialState)?{
??????hookStates[hookIndex]?=?hookStates[hookIndex]?||?initialState;
????
??????let?currentIndex?=?hookIndex;
??????function?dispatch(action)?{
????????hookStates[currentIndex]?=?reducer
????????????reducer(hookStates[currentIndex],?action)
??????????:?action;
????????//?觸發(fā)視圖更新
????????render();
??????}
??????return?[hookStates[hookIndex++],?dispatch];
????}
????
????//?useState可以使用useReducer改寫
????function?useState(initialState)?{
??????return?useReducer(null,?initialState);
????}
參考
Hook 規(guī)則
React Hooks 詳解 【近 1W 字】+ 項(xiàng)目實(shí)戰(zhàn)
推薦
React Hooks 父組件中獲取子組件實(shí)例值
React Hooks 中 useRef 的優(yōu)雅使用
后記
如果你喜歡探討技術(shù),或者對(duì)本文有任何的意見(jiàn)或建議,非常歡迎加魚頭微信好友一起探討,當(dāng)然,魚頭也非常希望能跟你一起聊生活,聊愛(ài)好,談天說(shuō)地。魚頭的微信號(hào)是:krisChans95 也可以掃碼關(guān)注公眾號(hào),訂閱更多精彩內(nèi)容。
