hook組件初渲染
hooks
組件在初次渲染時,
- 解析組件類型,判斷是
Function
還是class
類型,然后執行對應類型的處理方法 - 判斷到當前是
Function
類型組件后,首先在當前組件,也就是fiberNode
上進行hook
的創建和掛載,將所有的hook api
都掛載到全局變量dispatcher
上 - 順序執行當前組件,每遇到一個
hook api
都通過next
將它連接到當前fiberNode
的hook
鏈表上
hooks api 掛載
在初始渲染時,currentDispatcher
為空,就會先將所有hook api
掛載到當前fiberNode
的dispatcher
上。也就是將HooksDispatcherOnMountInDEV
賦值給dispatcher
{
// 首次執行currentDispatcher = null,所以進入else分支;在更新階段會進入if分支
if (currentDispatcher !== null) {
currentDispatcher = HooksDispatcherOnUpdateInDEV;
} else {
currentDispatcher = HooksDispatcherOnMountInDEV;
}
}
而在HooksDispatcherOnMountInDEV
中,包含各種原生hook
,內部調用mountXXX
方法。
HooksDispatcherOnMountInDEV = {
useCallback: function (callback, deps) {
return mountCallback(callback, deps);
},
useEffect: function (create, deps) {
return mountEffect(create, deps);
},
useMemo: function (create, deps) {
return mountMemo(create, deps);
},
useState: function (initialState) {
return mountState(initialState);
}
}
useState -- mountState
- 首先創建一個
hook
節點,其中的memoizedState
是最終返回的初始值;queue
是更新隊列,當我們多次更新某一狀態時需要通過queue
隊列存取和遍歷;next
用來連接下一個hook
; - 將當前的
hook
連接到當前的fiberNode
的hook
鏈表上 - 綁定狀態更新方法
dispatchAction
,並返回[state, dispatchAction]
function mountState(initailState){
var hook = {
moemoizedState: null,
queue: {
pending: null,
dispatch: null,
},
next: null
}
...
var dispatch = queue.dispatch = dispatchAction.bind(null, currentFiber,queue);
return [hook.memoizedState,dispatch];
}
useEffect -- mountEffectImpl
-
在
mountEffect
中,首先將當前的hook掛載到當前fiberNode
的hook
鏈表上 -
由於
useEffect
是異步執行的,會產生專屬於useEffect
的hook
。此時會將產生的useEffect
產生的hook存入componentUpdateQueue
更新隊列中。在某一次頁面渲染結束后會去遍歷這個更新隊列,執行傳入的effect hook
。
function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
// 創建並獲取當前hook節點信息
var hook = mountWorkInProgressHook();
hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);
}
function mountWorkInProgressHook() {
// 將當前hook連接到我們的hook鏈表中
var hook = {
memoizedState: null,
queue: null,
next: null
};
if (workInProgressHook === null) {
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
function pushEffect(tag, create, destroy, deps) {
var effect = {
tag: tag, // 更新標識
create: create, // 傳入的回調,也就是我們開發時的第一個參數
destroy: destroy, // return 的函數,組件銷毀時執行的函數
deps: deps, // 依賴項數組
next: null
};
var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
// 這里做的就是把每個useEffect hook單獨以鏈式結構存到了componentUpdateQueue這個全局變量中
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var lastEffect = componentUpdateQueue.lastEffect;
var firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
return effect;
}
初始化渲染至此結束,此時fiberNode
的hook
鏈式結構是
currentFiber: {
...
memoizedState: {
memoizedState: xxx,
...
next: {
memoizedState: xxx,
...
next: {
memoizedState: xxx,
...
next:hook4
}
}
}
}
hook組件更新階段
在組件更新時,於初始化類似,將更新的對應hook進行掛載,根據鏈表依次執行hook。在此時,需要執行狀態更新和useEffect
更新,最后更新完成。
useState
在初始化階段有提到,mountState
階段會綁定dispatchAction
並作為參數返回,其實也就是使用useState
時返回的setXXX
。
而在dispatchAction
中實際上,做了兩件事
- 創建update節點,並連接到
useState
的queue
后面,這樣每次調用dispatchAction
都會在后面連接一個update
節點,從而生成一個更新隊列。 - 然后根據更新任務的優先級排列任務,最后遍歷整個fiber樹執行更新操作。
執行setXXX后,發生了什么?
在初始化階段講到的,根據是否存在dispatcher,賦值。
{
// 更新階段dispatcher已存在,執行else部分
if (currentDispatcher !== null) {
currentDispatcher = HooksDispatcherOnUpdateInDEV;
} else {
currentDispatcher = HooksDispatcherOnMountInDEV;
}
}
而在HooksDispatcherOnMountInDEV
中各種hooks
對應的是update
方法,useState
對應的是updateState
方法。
function updateState(initialState){ return updateReducer(basicStateReducer);}
可以看到,updateState
實際上是返回了一個reducer
-
在
updateReducer
中,獲取到hook的更新隊列,比如執行了三次setCount
,則隊列中就會存在三項。 -
拿到更新隊列后,會對其進行循環遍歷,計算賦值,最終會將最新的state值復制到hook的
memoizedState
上並返回。
function updateReducer(reducer, initialArg, init) { // 獲取到當前hook,其實也就是直接.next就可以 var hook = updateWorkInProgressHook(); var queue = hook.queue; // 取到待更新的隊列 var pendingQueue = queue.pending; // 如果待更新隊列不為空,那么遍歷處理 if (pendingQueue !== null) { var first = pendingQueue.next; var newState = null; var update = first; queue.pending = null; // 循環遍歷,是更新階段的核心和關鍵, do { var action = update.action; newState = reducer(newState, action); update = update.next; } while (update !== null && update !== first); // 最新的狀態值賦值給memoizedState hook.memoizedState = newState; } // 將狀態值和更新方法返回,就和初次渲染一樣的流程 var dispatch = queue.dispatch; return [hook.memoizedState, dispatch];}
如何形成更新隊列?
而在updateReducer
之前,會執行dispatchAction
,將每個update
加入到更新隊列中。
// dispatchAction核心代碼var pending = queue.pending;// 這里是鏈表創建和連接的核心if (pending === null) { update.next = update;} else { update.next = pending.next; pending.next = update;}queue.pending = update;
每次dispatch
時,當目前更新隊列為空時,將當前update
加入,next
指向自己
當不為空時,將當前update
放到更新隊列最后一項的next
上。
需要注意的是,更新隊列中的first指向第一個執行的update
,queue.pending
指向最后一個。
所以在更新隊列循環執行時,結束循環的條件為while (update !== null && update !== first);
也就是當前update
為更新隊列中的第一個也就是唯一一個update
。
useEffect
與useState
相似,在更新過程中會執行updateEffect
,在其中執行updateEffectImpl
在updateEffectImpl
中,無論依賴項是否更改,都會調用pushEffect方法。
function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) { // 獲取到當前hook var hook = updateWorkInProgressHook(); // 比較依賴項是否發生了變化 if (areHookInputsEqual(nextDeps, prevDeps)) { // 如果相同則不對當前hook的屬性進行更新 pushEffect(hookEffectTag, create, destroy, nextDeps); return; } // 如果依賴項發生了變化,更新當前hook的memoizedState,這里的賦值只是做一個記錄,並沒有實際意義 currentlyRenderingFiber$1.effectTag |= fiberEffectTag; hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, destroy, nextDeps);}
而在pushEffect
方法中,
- 創建一個
effect
對象,並返回 - 創建/更新
componentUpdateQueue
隊列,其中存儲了useEffect
產生的回調,componentUpdateQueue
隊列不存在的話會進行創建,如果存在,會和mountState
階段一樣創建一個effect
的循環鏈表。effect
對象中的tag就是用來判斷這個useEffect
回調是否需要被執行。
一些可以被解釋了的問題
hook必須按照固定順序調用,不能在條件判斷中調用
由於每一個hook都是通過next指針在鏈表中按順序連接的,如果在某個條件判斷的情況下,某個hook不存在,就會導致整個hook鏈表中斷,沒法繼續正常遍歷hook鏈表,產生問題。