useState原理解析
一、初始化
构建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值。