一、初始化
構建dispatcher函數和初始值
二、更新時
-
調用dispatcher函數,按序插入update(其實就是一個action)
-
收集update,調度一次React的更新
-
在更新的過程中將
ReactCurrentDispatcher.current
指向負責更新的Dispatcher -
執行到函數組件App()時,
useState
會被重新執行,在resolve dispatcher的階段拿到了負責更新的dispatcher。 -
useState
會拿到Hook對象,Hook.query
中存儲了更新隊列,依次進行更新后,即可拿到最新的state -
函數組件App()執行后返回的nextChild中的count值已經是最新的了。FiberNode中的
memorizedState
也被設置為最新的state -
Fiber渲染出真實DOM。更新結束
三、 了解useState
useState的引入
// React.js import { useCallback, useContext, useEffect, useImperativeHandle, useDebugValue, useLayoutEffect, useMemo, useReducer, useRef, useState, } from './ReactHooks';
所有的Hooks在React.js
中被引入,掛載在React對象中
useState的實現
// ReactHooks.js export function useState<S>(initialState: (() => S) | S) { const dispatcher = resolveDispatcher(); return dispatcher.useState(initialState); }
重點都在這個dispatcher
上,dispatcher
通過resolveDispatcher()
來獲取,這個函數同樣也很簡單,只是將ReactCurrentDispatcher.current
的值賦給了dispatcher。
// ReactHooks.js function resolveDispatcher() { const dispatcher = ReactCurrentDispatcher.current; return dispatcher; }
ReactCurrentDispatcher.current.useState
是useState
能夠觸發更新的關鍵原因,這個方法的實現並不在react包內。
四. 核心步驟分析
ReactFiberHooks.js包含着各種關於Hooks邏輯的處理
Hook對象的結構如下:
// ReactFiberHooks.js export type Hook = { memoizedState: any, baseState: any, baseUpdate: Update<any, any> | null, queue: UpdateQueue<any, any> | null, next: Hook | null, };
在類組件中state
是一整個對象,可以和memoizedState
一一對應。但是在Hooks
中,React並不知道我們調用了幾次useState
,所以React通過將一個Hook對象掛載在memorizedStated
上來保存函數組件的state
重點關注memoizedState
和next
-
memoizedState
是用來記錄當前useState
應該返回的結果的 -
query
:緩存隊列,存儲多次更新行為 -
next
:指向下一次useState
對應的Hook對象。
renderWithHooks
renderWithHooks的運行過程如下:
// ReactFiberHooks.js export function renderWithHooks( current: Fiber | null, workInProgress: Fiber, Component: any, props: any, refOrContext: any, nextRenderExpirationTime: ExpirationTime, ): any { renderExpirationTime = nextRenderExpirationTime; currentlyRenderingFiber = workInProgress; // 如果current的值為空,說明還沒有hook對象被掛載 // 而根據hook對象結構可知,current.memoizedState指向下一個current nextCurrentHook = current !== null ? current.memoizedState : null; // 用nextCurrentHook的值來區分mount和update,設置不同的dispatcher ReactCurrentDispatcher.current = nextCurrentHook === null // 初始化時 ? HooksDispatcherOnMount // 更新時 : HooksDispatcherOnUpdate; // 此時已經有了新的dispatcher,在調用Component時就可以拿到新的對象 let children = Component(props, refOrContext); // 重置 ReactCurrentDispatcher.current = ContextOnlyDispatcher; const renderedWork: Fiber = (currentlyRenderingFiber: any); // 更新memoizedState和updateQueue renderedWork.memoizedState = firstWorkInProgressHook; renderedWork.updateQueue = (componentUpdateQueue: any); /** 省略與本文無關的部分代碼,便於理解 **/ }
初始化時
核心:創建一個新的hook,初始化state, 並綁定觸發器
初始化階段ReactCurrentDispatcher.current
會指向HooksDispatcherOnMount
對象
// ReactFiberHooks.js const HooksDispatcherOnMount: Dispatcher = { /** 省略其它Hooks **/ useState: mountState, }; // 所以調用useState(0)返回的就是HooksDispatcherOnMount.useState(0),也就是mountState(0) function mountState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { // 訪問Hook鏈表的下一個節點,獲取到新的Hook對象 const hook = mountWorkInProgressHook(); //如果入參是function則會調用,但是不提供參數 if (typeof initialState === 'function') { initialState = initialState(); } // 進行state的初始化工作 hook.memoizedState = hook.baseState = initialState; // 進行queue的初始化工作 const queue = (hook.queue = { last: null, dispatch: null, eagerReducer: basicStateReducer, // useState使用基礎reducer eagerState: (initialState: any), }); // 返回觸發器 const dispatch: Dispatch<BasicStateAction<S>,> = (queue.dispatch = (dispatchAction.bind( null, //綁定當前fiber結點和queue ((currentlyRenderingFiber: any): Fiber), queue, )); // 返回初始state和觸發器 return [hook.memoizedState, dispatch]; } // 對於useState觸發的update action來說(假設useState里面都傳的變量),basicStateReducer就是直接返回action的值 function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { return typeof action === 'function' ? action(state) : action; }
更新函數 dispatchAction
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) { /** 省略Fiber調度相關代碼 **/ // 創建新的新的update, action就是我們setCount里面的值(count+1, count+2, count+3…) const update: Update<S, A> = { expirationTime, action, eagerReducer: null, eagerState: null, next: null, }; // 重點:構建query // queue.last是最近的一次更新,然后last.next開始是每一次的action const last = queue.last; if (last === null) { // 只有一個update, 自己指自己-形成環 update.next = update; } else { const first = last.next; if (first !== null) { update.next = first; } last.next = update; } queue.last = update; /** 省略特殊情況相關代碼 **/ // 創建一個更新任務 scheduleWork(fiber, expirationTime); }
dispatchAction
中維護了一份query的數據結構。
query是一個有環鏈表,規則:
-
query.last指向最近一次更新
-
last.next指向第一次更新
-
后面就依次類推,最終倒數第二次更新指向last,形成一個環。
所以每次插入新update時,就需要將原來的first指向query.last.next。再將update指向query.next,最后將query.last指向update.
更新時
核心:獲取該Hook對象中的 queue,內部存有本次更新的一系列數據,進行更新
更新階段 ReactCurrentDispatcher.current
會指向HooksDispatcherOnUpdate
對象
// ReactFiberHooks.js // 所以調用useState(0)返回的就是HooksDispatcherOnUpdate.useState(0),也就是updateReducer(basicStateReducer, 0) const HooksDispatcherOnUpdate: Dispatcher = { /** 省略其它Hooks **/ useState: updateState, } function updateState(initialState) { return updateReducer(basicStateReducer, initialState); } // 可以看到updateReducer的過程與傳的initalState已經無關了,所以初始值只在第一次被使用 // 為了方便閱讀,刪去了一些無關代碼 // 查看完整代碼:https://github.com/facebook/react/blob/487f4bf2ee7c86176637544c5473328f96ca0ba2/packages/react-reconciler/src/ReactFiberHooks.js#L606 function updateReducer(reducer, initialArg, init) { // 獲取初始化時的 hook const hook = updateWorkInProgressHook(); const queue = hook.queue; // 開始渲染更新 if (numberOfReRenders > 0) { const dispatch = queue.dispatch; if (renderPhaseUpdates !== null) { // 獲取Hook對象上的 queue,內部存有本次更新的一系列數據 const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue); if (firstRenderPhaseUpdate !== undefined) { renderPhaseUpdates.delete(queue); let newState = hook.memoizedState; let update = firstRenderPhaseUpdate; // 獲取更新后的state do { const action = update.action; // 此時的reducer是basicStateReducer,直接返回action的值 newState = reducer(newState, action); update = update.next; } while (update !== null); // 對 更新hook.memoized hook.memoizedState = newState; // 返回新的 state,及更新 hook 的 dispatch 方法 return [newState, dispatch]; } } } // 對於useState觸發的update action來說(假設useState里面都傳的變量),basicStateReducer就是直接返回action的值 function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { return typeof action === 'function' ? action(state) : action; }
總結
單個hooks的更新行為全都掛在Hooks.queue下,所以能夠管理好queue的核心就在於
-
初始化queue - mountState
-
維護queue - dispatchAction
-
更新queue - updateReducer
結合示例代碼:
-
當我們第一次調用
[count, setCount] = useState(0)
時,創建一個queue -
每一次調用
setCount(x)
,就dispach一個內容為x的action(action的表現為:將count設為x),action存儲在queue中,以前面講述的有環鏈表規則來維護 -
這些action最終在
updateReducer
中被調用,更新到memorizedState
上,使我們能夠獲取到最新的state值。