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