React Hooks 原理

React Hooks 原理

目前,Hooks 應(yīng)該是 React 中最火的概念了,在閱讀這篇文章之前,希望你已經(jīng)了解了基本的 Hooks 用法。

在使用 Hooks 的時候,我們可能會有很多疑惑

為什么只能在函數(shù)最外層調(diào)用 Hook,不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用?

為什么 useEffect 第二個參數(shù)是空數(shù)組,就相當(dāng)于 ComponentDidMount ,只會執(zhí)行一次?

自定義的 Hook 是如何影響使用它的函數(shù)組件的?

Capture Value 特性是如何產(chǎn)生的?

......

這篇文章我們不會講解 Hooks 的概念和用法,而是會帶你從零實現(xiàn)一個 tiny hooks,知其然知其所以然。

useState

最簡單的 useState 用法是這樣的:

demo1:?https://codesandbox.io/s/v0nqm309q3

functionCounter(){var[count,setCount]=useState(0);return(<div><div>{count}</div><ButtononClick={()=>{setCount(count+1);}}>點擊</Button></div>);}

基于 useState 的用法,我們嘗試著自己實現(xiàn)一個 useState:

demo2:https://codesandbox.io/s/myy5qvoxpp

functionuseState(initialValue){varstate=initialValue;functionsetState(newState){state=newState;render();}return[state,setState];}

這時我們發(fā)現(xiàn),點擊 Button 的時候,count 并不會變化,為什么呢?我們沒有存儲 state,每次渲染 Counter 組件的時候,state 都是新重置的。

自然我們就能想到,把 state 提取出來,存在 useState 外面。

demo3:https://codesandbox.io/s/q9wq6w5k3w

var_state;// 把 state 存儲在外面functionuseState(initialValue){_state=_state||initialValue;// 如果沒有 _state,說明是第一次執(zhí)行,把 initialValue 復(fù)制給它functionsetState(newState){_state=newState;render();}return[_state,setState];}

到目前為止,我們實現(xiàn)了一個可以工作的 useState,至少現(xiàn)在來看沒啥問題。

接下來,讓我們看看 useEffect 是怎么實現(xiàn)的。

useEffect

useEffect 是另外一個基礎(chǔ)的 Hook,用來處理副作用,最簡單的用法是這樣的:

demo4:https://codesandbox.io/s/93jp55qyp4

useEffect(()=>{console.log(count);},[count]);

我們知道 useEffect 有幾個特點:

有兩個參數(shù) callback 和 dependencies 數(shù)組

如果 dependencies 不存在,那么 callback 每次 render 都會執(zhí)行

如果 dependencies 存在,只有當(dāng)它發(fā)生了變化, callback 才會執(zhí)行

我們來實現(xiàn)一個 useEffect

demo5:https://codesandbox.io/s/3kv3zlvzl1

let_deps;// _deps 記錄 useEffect 上一次的 依賴functionuseEffect(callback,depArray){consthasNoDeps=!depArray;// 如果 dependencies 不存在consthasChangedDeps=_deps? !depArray.every((el,i)=>el===_deps[i])// 兩次的 dependencies 是否完全相等:true;/* 如果 dependencies 不存在,或者 dependencies 有變化*/if(hasNoDeps||hasChangedDeps){callback();_deps=depArray;}}

到這里,我們又實現(xiàn)了一個可以工作的 useEffect,似乎沒有那么難。

此時我們應(yīng)該可以解答一個問題:

Q:為什么第二個參數(shù)是空數(shù)組,相當(dāng)于?componentDidMount??

A:因為依賴一直不變化,callback 不會二次執(zhí)行。

Not Magic, just Arrays

到現(xiàn)在為止,我們已經(jīng)實現(xiàn)了可以工作的 useState 和 useEffect。但是有一個很大的問題:它倆都只能使用一次,因為只有一個 _state 和 一個 _deps。比如

const[count,setCount]=useState(0);const[username,setUsername]=useState('fan');

count 和 username 永遠(yuǎn)是相等的,因為他們共用了一個 _state,并沒有地方能分別存儲兩個值。我們需要可以存儲多個 _state 和 _deps。

如 《React hooks: not magic, just arrays》所寫,我們可以使用數(shù)組,來解決 Hooks 的復(fù)用問題。

demo6:https://codesandbox.io/s/50ww35vkzl

代碼關(guān)鍵在于:

初次渲染的時候,按照 useState,useEffect 的順序,把 state,deps 等按順序塞到 memoizedState 數(shù)組中。

更新的時候,按照順序,從 memoizedState 中把上次記錄的值拿出來。

如果還是不清楚,可以看下面的圖。

letmemoizedState=[];// hooks 存放在這個數(shù)組letcursor=0;// 當(dāng)前 memoizedState 下標(biāo)functionuseState(initialValue){memoizedState[cursor]=memoizedState[cursor]||initialValue;constcurrentCursor=cursor;functionsetState(newState){memoizedState[currentCursor]=newState;render();}return[memoizedState[cursor++],setState];// 返回當(dāng)前 state,并把 cursor 加 1}functionuseEffect(callback,depArray){consthasNoDeps=!depArray;constdeps=memoizedState[cursor];consthasChangedDeps=deps? !depArray.every((el,i)=>el===deps[i]):true;if(hasNoDeps||hasChangedDeps){callback();memoizedState[cursor]=depArray;}cursor++;}

我們用圖來描述 memoizedState 及 cursor 變化的過程。

1. 初始化

?

2. 初次渲染

3. 事件觸發(fā)

4. Re Render

到這里,我們實現(xiàn)了一個可以任意復(fù)用的 useState 和 useEffect。

同時,也可以解答幾個問題:

Q:為什么只能在函數(shù)最外層調(diào)用 Hook?為什么不要在循環(huán)、條件判斷或者子函數(shù)中調(diào)用。

A:memoizedState 數(shù)組是按 hook定義的順序來放置數(shù)據(jù)的,如果 hook 順序變化,memoizedState 并不會感知到。

Q:自定義的 Hook 是如何影響使用它的函數(shù)組件的?

A:共享同一個 memoizedState,共享同一個順序。

Q:“Capture Value” 特性是如何產(chǎn)生的?

A:每一次 ReRender 的時候,都是重新去執(zhí)行函數(shù)組件了,對于之前已經(jīng)執(zhí)行過的函數(shù)組件,并不會做任何操作。

真正的 React 實現(xiàn)

雖然我們用數(shù)組基本實現(xiàn)了一個可用的 Hooks,了解了 Hooks 的原理,但在 React 中,實現(xiàn)方式卻有一些差異的。

React 中是通過類似單鏈表的形式來代替數(shù)組的。通過 next 按順序串聯(lián)所有的 hook。

typeHooks={memoizedState:any,// 指向當(dāng)前渲染節(jié)點 FiberbaseState:any,// 初始化 initialState, 已經(jīng)每次 dispatch 之后 newStatebaseUpdate:Update<any>| null,// 當(dāng)前需要更新的 Update ,每次更新完之后,會賦值上一個 update,方便 react 在渲染錯誤的邊緣,數(shù)據(jù)回溯queue:UpdateQueue<any>| null,// UpdateQueue 通過next:Hook| null,// link 到下一個 hooks,通過 next 串聯(lián)每一 hooks}typeEffect={tag:HookEffectTag,// effectTag 標(biāo)記當(dāng)前 hook 作用在 life-cycles 的哪一個階段create:()=>mixed,// 初始化 callbackdestroy:(()=>mixed)| null,// 卸載 callbackdeps:Array<mixed>| null,next:Effect,// 同上 };

memoizedState,cursor 是存在哪里的?如何和每個函數(shù)組件一一對應(yīng)的?

我們知道,react 會生成一棵組件樹(或Fiber 單鏈表),樹中每個節(jié)點對應(yīng)了一個組件,hooks 的數(shù)據(jù)就作為組件的一個信息,存儲在這些節(jié)點上,伴隨組件一起出生,一起死亡。

參考文章

Deep dive: How do React hooks really work?

React hooks: not magic, just arrays

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容