React源码阅读(4)-支线(函数式组件)-构建细节以及更新(中)
开篇
回顾一下,在构建细节和更新(上)中我们已经到了进入函数式组件Component,接下来我们就可以展开hooks讲,去看一下hooks是怎样处理更新操作。首先我们探讨一下,hooks在官网的定义中其实分为了两类,状态State Hook和副作用Effect Hook但我觉得可以再加一类那就是Ref hook,我们其实可以从fiber中定义的属性来分析对照链接-3.fiber中Hooks强相关,State Hook其实很好理解就是能实现一个数据持久化且不会有副作用的hook。而Effect Hook指的就是,影响fiber节点副作用属性,会直接影响到最后commit操作的函数。总的来说api的最终指向是修改fiber中的状态和副作用,从而改变整个渲染的过程和结果。
我们这里理清一个概念,为了方便理解把,这里我们讲到的hook指的链表上的一个节点,fiber.memoizedState是一整个链表,hooks是函数式组件的维护器控制器。
State Hook
我们先讲状态hook打开在线调试(需要科学上网),再对照着我们的属性总结对照表先讲一下数据结构,fiber上的memoizedState存储的是hook形成的链表,hook本身代表节点,hook中有一个更新队列queue和基队列baseQuene这2个队列通常是queue => baseQuene,而queue中的pending又是一个环形链表update是属于pending的节点,而这里我先讲2个状态hook,useState和useReducer。
1、useState
这个api实际上也被分为了2个函数mountState和updateState,会在初始化和对比更新分别调用,可以看上一篇文章。
1.1 mountState
通过调试当我们走到reconciler初次构建是使用的mountState,更新是updateState,可以关注前文里讲到的在renderWithHooks函数中确定更新和初始化的ReactCurrentDispatcher。源码链接

我们进入到mountState,有一个很重要的函数mountWorkInProgressHook,因为在初始化一系列api中都会走到mountWorkInProgressHook这个函数去创建hook并挂载到Fiber上的memoizedState上,不管是State Hook还是Effect Hook或者Ref hook,都会按照调用顺序,来构建出一个个hook存储在memoizedState链表中,这也是我们无法去在hooks上写判断的原因。中间有一个初始化baseState和memoizedState的操作memoizedState不用我们多说就是表示当前的状态,baseState是用来合并操作使用的,最终返回了hook中的memoizedState初始值和dispatch提交函数。接下来我们就分析一下dispatchAction这个函数的调用。
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
// 创建链表和里面的属性
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// 值
initialState = initialState();
}
// 刚创建里面的属性都为Null,这时候进行初始化,设置hook.memoizedState/hook.baseState
hook.memoizedState = hook.baseState = initialState;
// 设置更新队列
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: basicStateReducer, //这是内置Reducer
lastRenderedState: (initialState: any),
});
// 设置hook dispatch函数,实际就是调用dispatchAction,在开始就珂里化一下
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
// 返回当前状态以及dispatch修改状态的函数
return [hook.memoizedState, dispatch];
}
// 创建hook
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// 链首的hook
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// 将hook指向链尾
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
dispatchAction更新,我们关注4个阶段
阶段1:初始化环形链表的update节点。
阶段2:创建queue.pending环形链表,设置节点的指向。
阶段3:性能优化,这里调用了上个函数传来的lastRenderedReducer最终的调用是在basicStateReducer,这只是一个内置的处理函数返回值或者返回回调函数处理的值。
阶段4:进入调度。
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
) {
// 1、 创建update节点
const eventTime = requestEventTime();
const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
const update: Update<S, A> = {
lane,
action,
eagerReducer: null,
eagerState: null,
next: (null: any),
};
// 2. 确定环形链表的指向
const pending = queue.pending;
if (pending === null) {
// 头节点
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
const alternate = fiber.alternate;
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
// 改变全局状态
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
} else {
// 3.性能优化
// 判断是否为第一个update
if (
fiber.lanes === NoLanes &&
(alternate === null || alternate.lanes === NoLanes)
) {
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
// 临时变量得到更新前的state
const currentState: S = (queue.lastRenderedState: any);
// 得到更新后的state
const eagerState = lastRenderedReducer(currentState, action);
// 将当前更新节点的最新提交函数和状态指向改变,也是一个优化,是在render阶段的优化,会判断
// reducer update.eagerReducer相等就不用计算
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
// 如果更新前合后的值是相等的就不需要调度了
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
}
}
}
// 4.发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
scheduleUpdateOnFiber(fiber, lane, eventTime);
}
}
1.2.updateState
当我们组件更新的时候会走到updateState

function updateState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
return updateReducer(basicStateReducer, (initialState: any));
}
我们前面文章分析了一下调用过程,就是第一次初始化此时并不是走的函数式组件的case,而是走的IndetermiateComponent不确定组件,他调用useState的时候初始化对应的是mountState,而我们第二次去更新的时候因为tag已经被打上标记了,我们此时就会走到函数式组件更新,此时useState对应的则是updateState,然后我们的更新的函数updateState的返回值是updateReducer将更新的值和内置的提交函数传进去。
function updateReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
// 1. 通过next指针拿到hook对象,每进来一次就拿下一个
const hook = updateWorkInProgressHook();
const queue = hook.queue;
// 更新提交函数
queue.lastRenderedReducer = reducer;
// 渲染数上的hook
const current: Hook = (currentHook: any);
// 被打断的情况下,上次还没有完成的update
let baseQueue = current.baseQueue;
// 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue
//待执行的update
const pendingQueue = queue.pending;
//下面的整个过程都是连接拼接
if (pendingQueue !== null) {
if (baseQueue !== null) {
const baseFirst = baseQueue.next;
const pendingFirst = pendingQueue.next;
baseQueue.next = pendingFirst;
pendingQueue.next = baseFirst;
}
// 可能发生一个更高优先级任务打断当前任务的执行
// 所以要将 baseQueue 也赋值给 current fiber
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
// 3. 状态计算
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
// 计算state
do {
const updateLane = update.lane;
// 优先级提取update
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不够: 加入到baseQueue中, 等待下一次render再执行这次的update
const clone: Update<S, A> = {
lane: updateLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
// 把update放到下一次执行
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 更新优先级(mergeLanes优先级转换)
currentlyRenderingFiber.lanes = mergeLanes(
currentlyRenderingFiber.lanes,
updateLane,
);
} else {
// 优先级足够: 状态合并
if (newBaseQueueLast !== null) {
// 更新baseQueue
// update
const clone: Update<S, A> = {
lane: NoLane,
action: update.action,
eagerReducer: update.eagerReducer,
eagerState: update.eagerState,
next: (null: any),
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
// 执行这次的update,计算新state
if (update.eagerReducer === reducer) {
// 性能优化: 相等直接取算出来的state
newState = ((update.eagerState: any): S);
} else {
// 计算新的
const action = update.action;
// 调用reducer获取最新状态
newState = reducer(newState, action);
}
}
update = update.next;
} while (update !== null && update !== first);
// 更新属性
if (newBaseQueueLast === null) {
// newBaseQueueLast已经为空,更新baseState
newBaseState = newState;
} else {
// 未处理完下次执行
newBaseQueueLast.next = (newBaseQueueFirst: any);
}
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 把计算之后的结果更新到workInProgressHook上
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch: Dispatch<A> = (queue.dispatch: any);
return [hook.memoizedState, dispatch];
}
这整个函数有三个阶段
一阶段:updateWorkInProgressHook 的作用主要是取出页面fiber树中的 hooks 链表中对应的 hook 节点,挂载到 workInProgress fiber 上的 hooks 链表。
function updateWorkInProgressHook(): Hook {
let nextCurrentHook: null | Hook;
// 页面fiber树的Hook
if (currentHook === null) {
// 若 current 为 null,从 currentlyRenderingFiber.alternate 取 current
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
// 否则从 current fiber 中取下一个 hook
nextCurrentHook = currentHook.next;
}
// 迭代 workInProgress fiber 链表
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
// workInProgressHook 说明是首次创建
nextWorkInProgressHook 为 null = currentlyRenderingFiber.memoizedState;
} else {
// 取下一个 workInProgress Hook
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
// 只有 re-render 的情况下,nextWorkInProgressHook 不为 null,因为在之前的 render 过程中已经创建过 workInProgress hook了
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
// 正常情况下,currentlyRenderingFiber.memoizedState 为 null,需要到从 current fiber 中克隆一个新的创建
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) {
// 若 workInProgressHook 为 null,作为首节点赋值给 memoizedState
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
// 将 workInProgressHook 添加到链表尾
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
二阶段:链表拼接,将queue.pending拼接到current.baseQueue
三阶段: 状态计算优化。
2、useReducer
它和useState的实现几乎是完全一样的,都是负责创建hook初始化等,但唯一的不同就是它的reducer是由外部传入的,刚刚我们看到的useState是一个内置的。
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
): any));
return [hook.memoizedState, dispatch];
}
他们两个也都是可以互相转换的,看个例子,这两者是等价的,只是说提交的时候只能通过type提交
const [state, dispatch] = useState({ count: 0 })
const [state, dispatch] = useReducer(
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
},
{ count: 0 },
);
总结
到这里基本的State Hook,就已经告一段落了,这一节搞懂了,后面的effect hook以及ref hook都会非常轻松。看不懂有问题可以+联系方式我们一起交流,一起卷,或者觉得文章有哪些地方可以改进的欢迎交流。
转载自:https://juejin.cn/post/7173980032417660942