React源码解析(六):useState与useReducer源码解析
useState 和 useReducer 可以说是 React 最基础也是最常用的两个 Hook 了,在介绍这两个钩子函数源码之前,先引入几个源码当中的变量,以便大家更好的理解。
currentlyRenderingFiber是当前React正在处理的Fiber对象,等同于workInProgress,代码中为了更好的区分workInProgress和workInProgressHook,使用了currentlyRenderingFiber;- 一个 Fiber 对象所有的 Hook 会以链表的形式被保存在其
memoizedState属性上 currentHook全局属性指workInProgress将要替换的 Fiber 的Hook链表,即workInProgress.alternate.memoizedState,也就是current.memoizedState;workInProgressHook全局属性指向当前workInProgress的Hook链表,即workInProgress.memoizedState;- 虽然
workInProgress.memoizedState和current.memoizedState都以 State 结尾,但都是 Hook 链表,前者会根据后者生成,因此每次更新Fiber时链表节点(单个Hook对象)需要前后对应上,前后链表节点baseState和baseQueue属性也存在逻辑依赖。
几个重要的概念:
-
State
我们在编写 React 组件时交给 React 存储的状态,它们会被挂载在当前组件所对应的
Fiber上,具体在fiber.memoizedState[第n个链表节点].memoizedState上。 -
Action
action在 Redux 中是一个携带描述变化信息的普通对象,而在 React 内部的状态管理中比较直观,它是我们在dispatcher中传入的新的state值或者可返回新state值的函数。 -
Reducer
reducer在 Redux 中是一个纯函数,用于接收旧的action和state,返回新的state,而在 React 同样也是起这样的作用,React 在updateState和rerenderState中传入的basicStateReducer是这样的:function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S { return typeof action === 'function' ? action(state) : action; } -
Dispatcher
React 内部的
dispatcher派发器跟状态管理库 Flux 的dispatcher类似,接受一个action,并间接触发 React 组件状态的更新,它也是useState、useReducer返回给用户,用于更新状态的setXxxx函数。
useState & useReducer
useState 唯一参数就是初始状态 initialState,或者是返回初始状态 initialState 的函数;useReducer 可以接受三个参数,第一个是 reducer,第二个参数是初始状态 initialState,第三个可选参数是一个初始化初始状态 initialState 的函数;
useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>]
useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>]
函数组件中可以通过它们给组件添加状态,具体状态管理流程如下:


React 会维护一个 ReactCurrentDispatcher 派发器对象,此对象 current 属性会实时指向当前的具体 dispatcher,current 的值一般有以下四种:
const ContextOnlyDispatcher: Dispatcher = {
// 当我们在函数外面调用 hook 函数会报错,执行的就是这个`dispatcher`
useState: throwInvalidHookError,
useReducer: throwInvalidHookError
}
const HooksDispatcherOnMount: Dispatcher = {
useState: mountState,
useReducer: mountReducer
};
const HooksDispatcherOnUpdate: Dispatcher = {
useState: updateState,
useReducer: updateReducer
};
const HooksDispatcherOnRerender: Dispatcher = {
useState: rerenderState,
useReducer: rerenderReducer
};
// 排除了在DEV环境下的情况 ...
mountState & mountReducer
-
mountState和mountReducer首先会调用mountWorkInProgressHook创建一个 hook 对象,将当前React正在处理的workInProgress对象memoizedState属性指向该 hook 对象;const hook: Hook = { memoizedState: null, // hook对象当中也有一个memoizedState baseState: null, baseQueue: null, queue: null, next: null, }; -
mountState和mountReducer中hook对象memoizedState和baseState两个属性来源不同,mountState中是我们传入的初始状态initialState(也可以是action函数),而在mountReducer中是我们传入的第二个参数,但如果有第三个参数,那么会通过第三个参数初始化第二个参数得到初始状态initialState,即initialState = init(initialArg) -
初始化
hook对象queue属性,其中queue.lastRenderedState属性会指向初始状态initialState;queue.lastRenderedReducer属性在mountState中指向全局函数basicStateReducer,而在mountReducer中指向useReducer传入的第一个参数(reducer函数)const queue: UpdateQueue<S, A | BasicStateAction<S>> = { pending: null, interleaved: null, lanes: NoLanes, dispatch: null, lastRenderedReducer: basicStateReducer, // mountState lastRenderedReducer: reducer, // mountReducer lastRenderedState: (initialState: any), }; hook.queue = queue; -
将
hook.queue对象dispatch属性指向dispatchAction.bind(null, workInProgress, queue); -
返回一个小数组,第一个元素为
hook.memoizedState,第二个元素为hook.queue.dispatch。
updateState & updateReducer
-
updateState会直接调用updateReducer,传入basicStateReducer和初始状态initialState值;function updateState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateA ction<S>>] { return updateReducer(basicStateReducer, (initialState: any)); } -
updateReducer首先会调用updateWorkInProgressHook,此函数内部会从current树中拿到下一个currentHook和workInProgressHook,返回workInProgressHook; -
如果
workInProgressHook.queue.pending(源码中变量pendingQueue指向此链表)不为null,则将pendingQueue插入到currentHook.baseQueue第一个链表节点前,最终将pendingQueue设为 null;let baseQueue = current.baseQueue; const pendingQueue = queue.pending; // Merge the pending queue and the base queue. const baseFirst = baseQueue.next; const pendingFirst = pendingQueue.next; baseQueue.next = pendingFirst; pendingQueue.next = baseFirst; current.baseQueue = baseQueue = pendingQueue; queue.pending = null; -
如果
baseQueue不为null,则从baseQueue第一个链表节点开始向后遍历,每个节点都是一次dispatcher新的update,判断该update的优先级update.lane是否包含在renderLanes中:不包含会跳过更新,将未更新的update保存在baseQueue中,其优先级update.lane合并到workInProgress.lane中,等待下一次更新;包含则调用basicStateReducer获取新的 state; -
最终更新
workInProgressHook的memoizedState、baseState、baseQueue属性,workInProgressHook.queue.lastRenderedState也为新的state; -
返回一个小数组,数组第一项为
workInProgressHook.memoizedState,数组第二项为workInProgressHook.queue.dispatch对象。
rerenderState & renderReducer
-
rerenderState会直接调用rerenderReducer,传入basicStateReducer和传入的初始状态initialState值;function rerenderState<S>( initialState: (() => S) | S, ): [S, Dispatch<BasicStateAction<S>>] { return rerenderReducer(basicStateReducer, (initialState: any)); } -
rerenderReducer函数作用跟updateReducer函数差不多,只有两个不同点:- 会检查
workInProgressHook.queue.pending(源码中lastRenderPhaseUpdate指向此链表),如果不为 null,则对lastRenderPhaseUpdate进行向后遍历,调用basicStateReducer获取新的 state; - 在遍历时不检查
lastRenderPhaseUpdate中每个update的优先级,因为此时的update.lane肯定会包含在renderLanes中;
- 会检查
更新状态的函数:dispatcher
我们通常定义为 setXxxx,即 useState 和 useReducer 返回的数组第二项,是一个 dispatcher 函数,调用该函数会在一定条件下引发 React 组件的更新, React 的调度器 scheduler 也会参与这个过程。因为需要调度更新的优先级,此过程是异步的,最快会在下一次 React 组件函数执行时,useState 和 useReducer 才能返回新的 state。
-
首先这个函数会创建一个
update,如果断定当前的Fiber正在 render 阶段,则将update插入到此 Hook 的queue.pending链表中,并更新两个全局对象:didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true; -
如果当前的
Fiber不在 render 阶段:- 则根据是不是
ConcurrentMode选择要插入到queue.interleaved链表中还是queue.pending链表中; - 此时如果当前
Fiber没有任何优先级的更新需要处理,则提前通过reducer得到下一个状态值,并将此时的reducer和新的状态值隐藏在第一步创建的update中,如果新的状态值和旧的状态值一样,则直接结束 dispatch 过程; - 之后会调用
scheduleUpdateOnFiber函数,当前Fiber节点进入 render 阶段;
- 则根据是不是
-
render 阶段会调用 React 组件函数,此时
useState就可以拿到workInProgressHook.queue.pending上的每个新产生的update进行上文所说的处理。
转载自:https://juejin.cn/post/7190360446874943525