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