這是React Hooks的首要規則,這是因為React Hooks 是以單向循環鏈表的形式存儲,即是有序的。循環是為了從最后一個節點移到一個節點的時候,只需通過next一步就可以拿到第一個節點,而不需要一層層回溯。React Hooks的執行,分為?mount?和?update?階段,在mount階段的時候,通過mountWorkInProgressHook() 創建各個hooks (如useState, useMemo, useEffect, useCallback等),并且將當前hook添加到表尾。在update階段,在獲取或者更新hooks值的時候,會先獲取當前hook的狀態,hook.memoizedState,并且是按照順序或讀寫更新hook,若在條件或者循環語句使用hooks,那么在更新階段,若增加或者減少了某個hook,hooks的數量發生變化,而React是按照順序,通過next讀取下一個hook,則導致后面的hooks和掛載(mount)階段對應不上,發生讀寫錯值的情況,從而引發bug。
我們可以看看useState在mount階段的源碼:
function mountState<S>(initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {const hook = mountWorkInProgressHook();if (typeof initialState === 'function') {// $FlowFixMe: Flow doesn't like mixed typesinitialState = initialState();}hook.memoizedState = hook.baseState = initialState;const queue: UpdateQueue<S, BasicStateAction<S>> = {pending: null,lanes: NoLanes,dispatch: null,lastRenderedReducer: basicStateReducer,lastRenderedState: (initialState: any),};hook.queue = queue;const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchSetState.bind(null,currentlyRenderingFiber,queue,): any));return [hook.memoizedState, dispatch];
}
useCallback在mount階段的源碼:
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {const hook = mountWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;hook.memoizedState = [callback, nextDeps];return callback;
}
然后mountWorkInProgressHook的源碼如下:
function mountWorkInProgressHook(): Hook {const hook: Hook = {memoizedState: null,baseState: null,baseQueue: null,queue: null,next: null,};if (workInProgressHook === null) {// This is the first hook in the listcurrentlyRenderingFiber.memoizedState = workInProgressHook = hook;} else {// Append to the end of the listworkInProgressHook = workInProgressHook.next = hook;}return workInProgressHook;
}
其他hooks的源碼也是類似,以mountWorkInProgressHook創建當前hooks, 并且把hook的數據存到hook.memoizedState上,而在update階段,則是依次讀取hooks鏈表的memoizedState屬性來獲取狀態 (數據)。
React 為什么要以單向循環鏈表的形式存儲hooks呢?直接以key-value的對象形式存儲就可以在循環或者條件語句中使用hooks了,豈不更好?
這是因為react scheduler的調度策略如此,以鏈表的形式存儲是為了可以實現并發、可打斷、可恢復、可繼續執行下一個fiber任務。