react 版本:v17.0.3
1、前言
在 React Hooks 源码解读之Hook入口 一文中,我们介绍了 Hooks 的入口及hook处理函数的挂载,从 hook 处理函数的挂载关系我们可以得到这样的等式:
-
组件挂载阶段:
useReducer = ReactCurrentDispatcher.current.useReducer = HooksDispatcherOnMount.useReducer = mountReducer;
-
组件更新阶段:
useReducer = ReactCurrentDispatcher.current.useReducer = HooksDispatcherOnUpdate.useReducer = updateReducer
因此,组件在挂载阶段,执行 useReducer,实际上执行的是 mountReducer,而在更新阶段,实际上执行的是 updateReducer 。
2、类型定义
我们先来看看 ReactFiberHooks.new.js 中几个类型的定义。
2.1 Hook
我们在函数组件里定义了多个 hook,那么函数组件是如何找到对应的 hook 信息呢?我们来看看 hook 的类型定义:
// packages/react-reconciler/src/ReactFiberHooks.new.js
export type Hook = {|
memoizedState: any, // 对于 useState 和 useReducer 的值就是 state 的值
baseState: any,
baseQueue: Update < any, any > | null,
queue: UpdateQueue < any, any > | null, // hook 的更新队列
next: Hook | null, // 下一个 hook
|};
函数组件的 hook 信息存储在对应 Fiber 节点的 memoizedState 属性上,代表函数组件里的第一个 hook,hooks 的数据结构为单向链表,每一个节点可以通过 next 指针找到下一个 hook 。
2.2 Update & UpdateQueue
每个 hook 的更新队列都会存储在 queue 属性里,执行更新队列里的更新任务后,就可以得到最新的 state 值,一个更新任务及更新队列的类型定义如下:
// packages/react-reconciler/src/ReactFiberHooks.new.js
type Update<S, A> = {|
lane: Lane,
action: A,
eagerReducer: ((S, A) => S) | null,
eagerState: S | null,
next: Update < S, A >,
|};
export type UpdateQueue<S, A> = {|
pending: Update < S, A > | null, // 最新的更新任务
interleaved: Update < S, A > | null,
lanes: Lanes,
dispatch: (A => mixed) | null, //
lastRenderedReducer: ((S, A) => S) | null, // 上一次的 reducer
lastRenderedState: S | null, // 上一次的 state
|};
hook 的更新队列是一个环形链表,pending 字段存储的是最新的 update,通过 update.next 可以获取到第一个加入更新队列的 update。
3、挂载阶段
在挂载阶段,执行 useReducer,实际上执行的是 mountReducer,我们来看看这个函数的实现。
3.1 mountReducer
// packages/react-reconciler/src/ReactFiberHooks.new.js
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 创建一个 hook 对象,hook 会形成一个单向链表
const hook = mountWorkInProgressHook();
// const [state, dispatch] = useReducer(reducer, initialArg, init);
// 执行 useReducer 的参数初始化 state
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
// 将初始的 state 添加到 hook 对象上
hook.memoizedState = hook.baseState = initialState;
// 新建一个 更新队列,将传入的 reducer 和 初始化后的 state 挂到队列上,后续在 update 阶段会使用到
const queue = (hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
});
// 创建一个 dispatch ,即 useReducer 返回的用于触发 action 的函数
// const [state, dispatch] = useReducer(reducer, initialArg, init);
// dispatch 上绑定了当前 fiber 节点和新建的更新队列
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
// 返回当前的 state 以及用于触发 action 的 dispatch 方法
return [hook.memoizedState, dispatch];
}
组件在挂载阶段,首先创建了一个新的 hook 对象,hook 会形成一个单向链表。然后初始化 state,将 state 分别添加到 hook 对象的 memoizedState 属性 和 baseState 属性。接着新建一个更新队列,将传入的 reducer 和 初始的state挂到队列上,后续的更新任务也将会添加到该更新队列上。再接着创建了一个用于触发 action 的dispatch 函数,并把当前的 fiber 节点和新建的更新队列绑定到了 dispatch 上。在最后,返回了当前的 state 以及dispatch 方法。
3.2 mountWorkInProgressHook
在 mountReducer() 函数中,使用 mountWorkInProgressHook() 创建了一个新的 hook 对象,我们来看看它是如何被创建的:
// packages/react-reconciler/src/ReactFiberHooks.new.js
// 创建一个新的 hook 对象,并返回当前的 workInProgressHook 对象
// workInProgressHook 对象是全局对象,在 mountWorkInProgressHook 中首次初始化
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
// Hooks are stored as a linked list on the fiber's memoizedState field
// 将 新建的 hook 对象以链表的形式存储在当前的 fiber 节点memoizedState属性上
// 只有在第一次打开页面的时候,workInProgressHook 为空
if (workInProgressHook === null) {
// This is the first hook in the list
// 链表上的第一个 hook
// currentlyRenderingFiber: The work-in-progress fiber. I've named it differently to distinguish it fromthe work-in-progress hook.
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
// 已经存在 workInProgressHook 对象,则将新创建的这个 Hook 接在 workInProgressHook 的尾部,形成链表
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
可以看到,在新建一个 hook 对象时,如果全局的 workInProgressHook 对象不存在 (值为 null),即组件在首次渲染时,将新建的 hook 对象赋值给 workInProgressHook 对象,同时将 hook 对象赋值给 currentlyRenderingFiber 的 memoizedState 属性,如果 workInProgressHook 对象已经存在,则将 hook 对象接在 workInProgressHook 的尾部,从而形成一个单向链表。
3.3 dispatchAction
在 mountReducer 中,通过 dispatchAction 创建了一个 dispatch 触发器,即更新 state 的函数,在创建 dispatch 触发器的时候,绑定了当前的 fiber 节点和新建的 queue 队列,我们来重点看看这个 dispatchAction:
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
const eventTime = requestEventTime();
// 获取优先级
const lane = requestUpdateLane(fiber);
// 创建一个新的update
// action就是我们setCount里面的值(count+1, count+2, count+3…)
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 备用的 fiber
const alternate = fiber.alternate;
/**
* 情况一: render 阶段的处理
* if分支 if (fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)
* currentlyRenderingFiber 即 workInProgress,workInProgress存在代表当前处于render阶段
* 在 render 阶段,做了以下处理:
* 1、标记变量 didScheduleRenderPhaseUpdate,后续单独处理
* 2、新建或更新环形链表的 updateQueue
*/
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 涉及 Fiber 的调度
// fiber === currentlyRenderingFiber 时是 re-render,即当前更新周期中又产生了新的周期
// 如果是 re-render ,didScheduleRenderPhaseUpdateDuringThisPass 置为 ture,
// 就会循环计数 numberOfReRenders 来记录 re-render 的次数
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
/**
* 在 render 阶段对 updateQueue 的处理
*/
const pending = queue.pending; // pending 是 updateQueue 中的一个 update
if (pending === null) {
// This is the first update. Create a circular list.
// 这是首次更新,创建循环链表
// 只有一个 update,自己指向自己,形成环形链表
update.next = update;
} else {
// 更新循环链表
update.next = pending.next;
// 形成环形链表
pending.next = update;
}
// 将环形链表的一个 更新重新赋值给 queue.pending
queue.pending = update;
} else {
/**
* re-render 阶段的更新 做处理
*/
// 判断是否是re-render 阶段的更新
if (isInterleavedUpdate(fiber, lane)) {
// 优先级决定的更新
const interleaved = queue.interleaved;
if (interleaved === null) {
// This is the first update. Create a circular list.
// 新建环形链表
update.next = update;
// At the end of the current render, this queue's interleaved updates will
// be transfered to the pending queue.
// 在当前渲染结束时,此队列的交错更新将传输到挂起队列
pushInterleavedQueue(queue);
} else {
// 更新 环形链表
update.next = interleaved.next;
interleaved.next = update;
}
// 更新队列
queue.interleaved = update;
} else {
/**
* 在更新阶段根据优先级对 update 进行处理
*/
// 创建/更新存储update的环形链表
const pending = queue.pending;
if (pending === null) {
// 这是首次更新,创建循环链表
// This is the first update. Create a circular list.
update.next = update;
} else {
// 更新环形链表
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
// fiber.lanes保存fiber上存在的update的优先级
// fiber.lanes === NoLanes意味着fiber上不存在update
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 计算进入渲染阶段之前的下一个阶段的 state,如果新 state 与 当前状态相同,则退出渲染
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
if (__DEV__) {
ReactCurrentDispatcher.current = prevDispatcher;
}
}
}
}
// 获取当前 fiber,根据优先级区分同步任务和异步任务,同步任务应立即同步执行,最先渲染出来,异步任务走scheduler
// https://juejin.cn/post/6898635086657224717#heading-16
// scheduleUpdateOnFiber 去调度执行更新任务
const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (isTransitionLane(lane) && root !== null) {
let queueLanes = queue.lanes;
// If any entangled lanes are no longer pending on the root, then they
// must have finished. We can remove them from the shared queue, which
// represents a superset of the actually pending lanes. In some cases we
// may entangle more than we need to, but that's OK. In fact it's worse if
// we *don't* entangle when we should.
queueLanes = intersectLanes(queueLanes, root.pendingLanes);
// Entangle the new transition lane with the other transition lanes.
const newQueueLanes = mergeLanes(queueLanes, lane);
queue.lanes = newQueueLanes;
// Even if queue.lanes already include lane, we don't know for certain if
// the lane finished since the last time we entangled it. So we need to
// entangle it again, just to be sure.
markRootEntangled(root, newQueueLanes);
}
}
// 给当前的 update 做标记,在 performance 上可以看到改 update 的信息
if (enableSchedulingProfiler) {
markStateUpdateScheduled(fiber, lane);
}
}
在 dispatchAction 中维护了一份 queue 的数据结构:
/**
* 在 render 阶段对 updateQueue 的处理
*/
const pending = queue.pending; // pending 是 updateQueue 中的一个 update
if (pending === null) {
// This is the first update. Create a circular list.
// 这是首次更新,创建循环链表
// 只有一个 update,自己指向自己,形成环形链表
update.next = update;
} else {
// 更新循环链表
update.next = pending.next;
// 形成环形链表
pending.next = update;
}
// 将环形链表的一个 更新重新赋值给 queue.pending
queue.pending = update;
queue 是一个环形链表,其规则为:
- queue.pending 指向最近一次更新
- pending.next指向第一次更新
if 的第一个分支 fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)
是对 Fiber 调度的处理,fiber === currentlyRenderingFiber
时是 re-render,即当前更新周期中又产生了新的周期,如果是 re-render,didScheduleRenderPhaseUpdateDuringThisPass 置为 true,而在 renderWithHooks 中 如果 didScheduleRenderPhaseUpdateDuringThisPass 为 true,就会循环计数 numberOfReRenders 来记录 re-render 的次数。
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 这是一个render阶段触发的更新,需要标记变量didScheduleRenderPhaseUpdate,后续单独处理
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
}
如果是re-render 阶段的更新,当 fiber 上不存在优先级的 update 时,就去计算进入渲染阶段之前的下一个阶段的 state,如果计算出来的状态(eagerState) 与当前的状态(currentState) 相同,则会退出渲染。
// fiber.lanes保存fiber上存在的update的优先级
// fiber.lanes === NoLanes意味着fiber上不存在update
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
// 计算进入渲染阶段之前的下一个阶段的 state,如果新 state 与 当前状态相同,则退出渲染
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState: S = (queue.lastRenderedState: any);
const eagerState = lastRenderedReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
return;
}
}
}
}
我们整理一下首次渲染时的一个流程:
4、更新阶段
接下来我们来看看更新过程中 useReducer 实际调用的方法 updateReducer。
4.1 updateReducer
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 获取当前正在工作中的 Hook,即 workInProgressHook
const hook = updateWorkInProgressHook();
const queue = hook.queue;
invariant(
queue !== null,
'Should have a queue. This is likely a bug in React. Please file an issue.',
);
queue.lastRenderedReducer = reducer;
// currentHook 全局变量,当前 fiber 上的 hook 列表
const current: Hook = (currentHook: any);
// The last rebase update that is NOT part of the base state.
let baseQueue = current.baseQueue;
// The last pending update that hasn't been processed yet.
// 尚未处理的最后一个待处理更新
const pendingQueue = queue.pending;
// 情形一:
// 有待处理的更新,但此时还没有更新的队列在处理,将待处理的更新添加到已有的基础队列的后面
if (pendingQueue !== null) {
// We have new updates that haven't been processed yet.
// We'll add them to the base queue.
if (baseQueue !== null) {
// Merge the pending queue and the base queue.
// 将待处理的更新添加基础队列的后面,维护环形链表
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
// pendingQueue赋值给baseQueue
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// 情形二:
// 在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(base queue)上的 update
if (baseQueue !== null) {
// We have a queue to process.
// queue.pending为最新的update,next则为第一个update
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
// 根据优先级处理环形链表 base queue 上的 update
do {
const updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
/**
* 对低优先级的 update 进行处理:
* 1、当前的 update 将会被跳过
* 2、将当前的 update 拷贝一份,添加到更新队列的尾部
*/
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
/**
* 对高优先级的 update 进行处理:
* 1、赋值一份当前的 update 进行备份
* 2、调用 reducer 计算出新的 state
*/
// This update does have sufficient priority.
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 使用 reducer 计算新的 state
// Process this update.
// update.eagerReducer只有在第一次调用dispatchAction发起更新的时候才会赋值
// 当reducer没有发生变化的时候
if (update.eagerReducer === reducer) {
// If this update was processed eagerly, and its reducer matches the
// current reducer, we can use the eagerly computed state.
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
newState = reducer(newState, action);
}
}
// 获取链表上的下一个 update
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
// 前后的 state 不同时标识变化
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 更新队列上的update 处理完后,在 hook 对象行挂载新的 state 和 update queue
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
// 情形三
// 对于 Interleaved 类型的 update,在渲染过程中不做处理
// Interleaved updates are stored on a separate queue. We aren't going to
// process them during this render, but we do need to track which lanes
// are remaining.
const lastInterleaved = queue.interleaved;
if (lastInterleaved !== null) {
let interleaved = lastInterleaved;
do {
const interleavedLane = interleaved.lane;
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
interleavedLane,
);
markSkippedUpdateLanes(interleavedLane);
interleaved = ((interleaved: any).next: Update < S, A >);
} while (interleaved !== lastInterleaved);
} else if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to
// zero once the queue is empty.
queue.lanes = NoLanes;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
// 返回新的 state 和 dispatch 函数 //useReducer 的使用 const [state, dispatch] = useReducer(reducer, initialArg, init);
return [hook.memoizedState, dispatch];
}
updateReducer 分为三种情形:
-
产生了一个新的更新(update),将它添加到 更新队列(base queue) 中。
-
在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(base queue)上的 update。
-
对于 interleaved 类型的 update,则不做处理,仅仅标记跟踪其优先级。
我们重点来关注下 updateReducer 的第二种情形:
// 情形二:
// 在处理「更新」的过程中没有产生新的 update,根据优先级来处理 更新队列(base queue)上的 update
if (baseQueue !== null) {
// We have a queue to process.
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
// 根据优先级处理环形链表 base queue 上的 update
do {
const updateLane = update.lane;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
/**
* 对低优先级的 update 进行处理:
* 1、当前的 update 将会被跳过
* 2、将当前的 update 拷贝一份,添加到更新队列的尾部
*/
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// Update the remaining priority in the queue.
// TODO: Don't need to accumulate this. Instead, we can remove
// renderLanes from the original lanes.
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
markSkippedUpdateLanes(updateLane);
} else {
/**
* 对高优先级的 update 进行处理:
* 1、赋值一份当前的 update 进行备份
* 2、调用 reducer 计算出新的 state
*/
// This update does have sufficient priority.
if (newBaseQueueLast !== null) {
const clone: Update<S, A> = {
// This update is going to be committed so we never want uncommit
// it. Using NoLane works because 0 is a subset of all bitmasks, so
// this will never be skipped by the check above.
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 使用 reducer 计算新的 state
// Process this update.
if (update.eagerReducer === reducer) {
// If this update was processed eagerly, and its reducer matches the
// current reducer, we can use the eagerly computed state.
newState = ((update.eagerState: any): S);
} else {
const action = update.action;
newState = reducer(newState, action);
}
}
// 获取链表上的下一个 update
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 更新队列上的update 处理完后,在 hook 对象行挂载新的 state 和 update queue
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
对更新队列上的 update 进行 处理时,会根据优先级的高低分别做不同的处理。
-
对于低优先级的 update:
-
低优先级的update拷贝一份,将其添加到更新队列尾部
-
不会计算新的state
if (!isSubsetOfLanes(renderLanes, updateLane)) {
/**
-
对低优先级的 update 进行处理:
-
1、当前的 update 将会被跳过
-
2、将当前的 update 拷贝一份,保存到新的更新队列上 */
// Priority is insufficient. Skip this update. If this is the first // skipped update, the previous update/state is the new base // update/state. const clone: Update<S, A> = { lane: updateLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; if (newBaseQueueLast === null) { newBaseQueueFirst = newBaseQueueLast = clone; newBaseState = newState; } else { newBaseQueueLast = newBaseQueueLast.next = clone; } // Update the remaining priority in the queue. // TODO: Don't need to accumulate this. Instead, we can remove // renderLanes from the original lanes. currentlyRenderingFiber.lanes = mergeLanes( currentlyRenderingFiber.lanes, updateLane, ); markSkippedUpdateLanes(updateLane); }
-
-
对于高优先级的 update
-
拷贝当前的update,将其添加到更新队列的尾部
-
调用 reducer 计算新的 state
else {
/**
- 对高优先级的 update 进行处理:
- 1、赋值一份当前的 update 进行备份
- 2、调用 reducer 计算出新的 state */
// This update does have sufficient priority.
if (newBaseQueueLast !== null) { const clone: Update<S, A> = { // This update is going to be committed so we never want uncommit // it. Using NoLane works because 0 is a subset of all bitmasks, so // this will never be skipped by the check above. lane: NoLane, action: update.action, eagerReducer: update.eagerReducer, eagerState: update.eagerState, next: (null: any), }; newBaseQueueLast = newBaseQueueLast.next = clone; }
// 使用 reducer 计算新的 state // Process this update. if (update.eagerReducer === reducer) { // If this update was processed eagerly, and its reducer matches the // current reducer, we can use the eagerly computed state. newState = ((update.eagerState: any): S); } else { const action = update.action; newState = reducer(newState, action); } }
4.2 updateWorkInProgressHook
在 updateReducer() 函数中,通过 updateWorkInProgressHook() 获取到了获取当前正在工作中的 Hook,即 workInProgressHook,我们来看看 updateWorkInProgressHook 的实现:
function updateWorkInProgressHook(): Hook {
// This function is used both for updates and for re-renders triggered by a
// render phase update. It assumes there is either a current hook we can
// clone, or a work-in-progress hook from a previous render pass that we can
// use as a base. When we reach the end of the base list, we must switch to
// the dispatcher used for mounts.
// 获取 当前 hook 的下一个 hook
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
// 取下一个 hook 为当前的hook
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// There's already a work-in-progress. Reuse it.
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// Clone from the current hook.
// 拷贝当前的 hook,作为当前正在工作中的 workInProgressHook
invariant(
nextCurrentHook !== null,
'Rendered more hooks than during the previous render.',
);
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list.
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// Append to the end of the list.
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
这里分两种情况:
- 如果是在 render 阶段,则会取下一个 hook 作为当前的hook,并返回 workInProgressHook;
- 如果是在 re-render 阶段,则在当前处理周期中,继续取当前的 workInProgressHook 做更新处理,最后再返回 workInProgressHook。
对于 useReducer 的解析到这就结束了,最后我们总结一下 useReducer 的执行流程:
5、总结
函数组件通过 renderWithHooks 函数可以确定当前的 workInProgress fiber 节点,通过是否存在 current fiber 节点来判断当前是在 Mount 阶段还是 Update 阶段,并获取相应阶段的 ReactCurrentDispatcher,执行函数组件自己来获取自身的 children 。
在执行过程中,会执行对应阶段的 hook 函数,函数组件的 hooks 是单向链表结构,存储在 Fiber 节点的 memoizedState 属性上,通过 next指针(hook.next) 依序获取下一个 hook 对象。在hook对象的 queue 属性上,存储着 hook 的更新队列,它是环形单向链表,queue.pending 指向最新的 update,queue.pending.next 则指向的是第一个 update 。
通过执行 mountWorkInProgressHook 来创建一个新的 hook 对象,然后返回初始 state 和触发 action 的 dispatch,通过执行 updateReducer 来处理 queue 中的 upate 以获取最新的state。
调用 dispatch 触发 action,发起更新任务调度,同时在 dispatchAction 里计算最新的state,并更新queue环形链表,然后执行 scheduleUpdateOnFiber,进入调度,再次进入到renderWithHooks,执行updateReducer,得到新的state值返回,并重新计算渲染。