🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
上一篇【react18原理探究实践】JS中的位运算&react中的lane模型 我们讲解了js中位运算的使用,进而延伸到位运算在react中的使用&优先级调度机制,本篇我们将探究react中的更新调度机制
🥑 你能学到什么?
希望在你阅读本篇文章之后,不会觉得浪费了时间。如果你跟着读下来,你将会学到:
- 更新对象
- 更新队列
- 如何调度更新
- 如何获取新的state
- state如何保证不丢失和有序
✍️系列文章react实现原理系列
- 【react18原理探究实践】使用babel手搓探索下jsx的原理
- 【react18原理探究实践】上手调试源码探究jsx原理
- 【react18原理探究实践】图解react几个核心包之间的关联
- 【react18原理探究实践】react启动流程,其实就是创建三大全局对象
- 【react18原理探究实践】JS中的位运算&react中的lane模型
一、触发更新
在react中有这么多种方式触发更新,react内部总不可能搞好几种更新方式吧,那在react内部更新机制是如何统一的呢?
ReactDOM.render
this.setState
this.forceUpdate
useState
useReducer
每次状态更新
都会创建一个保存更新状态相关内容的对象update,通过update我们可以计算出本次更新的state
二、update更新对象
1.update长啥样?
调式到updateContainer
时我们会看到一个createUpdate
函数,更新对象就是由它创建的
ts
export type Update<State> = {|
// 事件触发的时间,用于在调度中确定优先级
eventTime: number,
// 此更新的优先级级别
lane: Lane,
// 更新的类型
// 0: 更新
// 1: 替换
// 2: 强制更新
// 3: 捕获更新
tag: 0 | 1 | 2 | 3,
// 更新的负载,可以是新的状态、部分状态更新函数,或者其他数据
payload: any,
// 更新完成后的回调函数,可以为空
callback: (() => mixed) | null,
// 指向下一个更新对象,用于形成链表结构
next: Update<State> | null,
|};
js
{
eventTime: 57701.59999999404,
lane: 16,
tag: 0,
payload: null,
callback: null,
next: null,
}
属性的含义如下:
- eventTime:用于
ensureRootIsScheduled
计算过期时间用 - lane:优先级
- payload:setState的第一个参数
- callback:setState的第二个参数
- next:连接的下一个 update 对象
2.createUpdate
3.不同组件的不同update
由于类组件(Class Component)和根节点(Host Root)与函数组件(Function Component)工作方式不同,确实存在两种不同结构的 Update
,这里我们先简单列举下,现在我们主要讲解类组件和root使用的update对象,函数的我们在hooks原理那部分再讲解
类组件和根节点的 Update 结构
类组件和根节点共享相同的 Update
结构。这种结构主要用于 setState
和 forceUpdate
调用。它们通常涉及以下字段:
typescript
export type Update<State> = {|
eventTime: number, // 事件触发的时间
lane: Lane, // 更新的优先级
tag: 0 | 1 | 2 | 3, // 更新类型(0: 更新,1: 替换,2: 强制更新,3: 捕获更新)
payload: any, // 更新的负载(新的状态或部分状态更新函数)
callback: (() => mixed) | null, // 更新后的回调函数
next: Update<State> | null, // 下一个更新对象,跟其他update对象形成链表
|};
函数组件的 Update 结构
函数组件使用 React Hooks(如 useState
和 useReducer
)进行状态管理和更新,具有不同的 Update
结构。这种结构主要用于管理 Hook 状态的更新:
typescript
export type HookUpdate<State> = {
action: (state: State) => State, // 一个函数,接收当前状态并返回新的状态
next: HookUpdate<State> | null, // 下一个更新对象
};
在这种结构中,action
是一个函数,用于计算新的状态,next
指向下一个 HookUpdate
对象,从而形成一个更新链。
区别与联系
- 类组件和根节点 使用一个更复杂的
Update
结构,因为它们需要处理更多类型的更新操作(如setState
、forceUpdate
)。 - 函数组件 使用更简洁的
Update
结构,因为它们的状态更新通过 Hooks 进行,主要涉及状态更新函数。
4.为什么update对象要形成链表?
处理多次状态更新
在实际应用中,一个组件可能会在一次渲染周期内多次调用 setState
或其他更新方法。将这些更新对象形成链表,可以确保所有的更新都被依次处理。例如:
javascript
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 2 });
通过链表结构,这两个更新会被依次处理,而不会覆盖之前的更新。
2. 批处理更新
React 通过批处理来优化性能,在事件处理函数中调用 setState
多次只会触发一次重渲染。使用链表结构可以将多个更新对象批量处理,在批处理结束后统一进行一次重渲染,从而提高性能和响应速度。
3. 优先级和调度
链表结构使得 React 可以更灵活地管理更新的优先级。每个 Update
对象都有一个 lane
字段,表示其优先级。调度器可以遍历链表,根据优先级决定哪些更新应该优先处理,从而实现更高效的调度策略。
4. 易于扩展和维护
链表结构具有简单且灵活的特点,便于扩展和维护。在需要新增更新或修改更新顺序时,只需调整链表指针即可,无需对整个结构进行大规模修改。
5.一个简单的示例【重要】
js
type Update<State> = {
// 事件触发的时间,用于调度优先级
eventTime: number,
// 更新的优先级级别
lane: number,
// 更新的类型
// 0: 更新,1: 替换,2: 强制更新,3: 捕获更新
tag: 0 | 1 | 2 | 3,
// 更新的负载,可以是新的状态、部分状态更新函数或其他数据
payload: any,
// 更新完成后的回调函数,可以为空
callback: (() => mixed) | null,
// 指向下一个更新对象,用于形成链表结构
next: Update<State> | null,
};
// 定义一个更新队列类,用于管理状态更新的链表
class UpdateQueue<State> {
// 链表的第一个更新对象
firstUpdate: Update<State> | null = null;
// 链表的最后一个更新对象
lastUpdate: Update<State> | null = null;
// 将新的更新对象加入链表
enqueueUpdate(update: Update<State>) {
if (this.lastUpdate === null) {
// 如果链表为空,将新更新对象设为第一个和最后一个
this.firstUpdate = this.lastUpdate = update;
} else {
// 否则,将新更新对象添加到链表末尾
this.lastUpdate.next = update;
this.lastUpdate = update;
}
}
// 处理链表中的所有更新对象并返回新的状态
processUpdates(state: State): State {
let update = this.firstUpdate;
while (update !== null) {
// 根据更新对象处理状态
state = this.processUpdate(state, update);
// 移动到下一个更新对象
update = update.next;
}
return state;
}
// 根据更新对象的类型和负载处理状态
processUpdate(state: State, update: Update<State>): State {
switch (update.tag) {
case 0:
// 更新操作,将新的状态部分合并到现有状态
return { ...state, ...update.payload };
case 1:
// 替换操作,用新状态完全替换现有状态
return update.payload;
case 2:
// 强制更新操作,保留现有状态
return state;
case 3:
// 捕获更新操作,保留现有状态
return state;
default:
return state;
}
}
}
// 使用示例
const queue = new UpdateQueue<{ count: number }>();
// 添加第一个更新对象
queue.enqueueUpdate({
eventTime: Date.now(),
lane: 1,
tag: 0,
payload: { count: 1 },
callback: null,
next: null,
});
// 添加第二个更新对象
queue.enqueueUpdate({
eventTime: Date.now(),
lane: 1,
tag: 0,
payload: { count: 2 },
callback: null,
next: null,
});
// 初始状态
const initialState = { count: 0 };
// 处理所有更新对象并获取新的状态
const newState = queue.processUpdates(initialState);
console.log(newState); // 输出: { count: 2 }
在这个示例中,我们创建了一个 UpdateQueue
类来管理更新对象的链表。通过 enqueueUpdate
方法,我们可以将新的更新对象添加到链表中。processUpdates
方法遍历链表并处理每个更新对象,最终返回新的状态。react
内部也是类这样的处理结构,不过存在优先级调度等。
三、updateQueue
1.Update与Fiber的联系
每个 Fiber
对象包含一个 updateQueue
,用于存储该组件的所有 Update
对象。updateQueue
是一个链表结构,可以存储多个状态更新请求。
js
type Fiber = {
// 其他字段...
updateQueue: UpdateQueue<any> | null,
// 其他字段...
};
2.updateQueue长啥样?
剩下两种类型和Update
的两种类型对应。
ClassComponent
与HostRoot
使用的UpdateQueue
结构如下:
ts
export type UpdateQueue<State> = {
baseState: State,
firstBaseUpdate: Update<State> | null,
lastBaseUpdate: Update<State> | null,
shared: SharedQueue<State>,
effects: Array<Update<State>> | null,
|};
js
/**
* 初始化组件的更新队列。
*
* @param {Fiber} fiber 要初始化更新队列的组件 Fiber 对象。
* @returns {void}
*/
export function initializeUpdateQueue<State>(fiber: Fiber): void {
// 创建一个更新队列对象
const queue: UpdateQueue<State> = {
// 基础状态,初始值为组件的 memoizedState
baseState: fiber.memoizedState,
// 第一个基础更新对象
firstBaseUpdate: null,
// 最后一个基础更新对象
lastBaseUpdate: null,
// 共享对象,包含 pending 更新、interleaved 更新和更新的优先级 lanes
shared: {
pending: null,
interleaved: null,
lanes: NoLanes,
},
// 用于存储副作用的对象
effects: null,
};
// 将更新队列对象赋值给组件的 updateQueue 属性
fiber.updateQueue = queue;
}
3.enqueueUpdate函数
js
/**
* 在一个 fiber 上排队一个更新。此函数负责将更新添加到 fiber 的更新队列中,
* 并确保更新会在当前或下一次渲染阶段被处理。
*
* @param fiber - 更新所属的 fiber。
* @param update - 要排队的更新。
* @param lane - 表示更新优先级的通道。
* @returns 如果更新成功排队,返回 fiber 树的根,否则返回 null。
*/
export function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane,
): FiberRoot | null {
// 获取 fiber 的更新队列。如果 fiber 已经被卸载,updateQueue 将为 null。
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
// 如果更新队列为 null,表示该 fiber 已经被卸载。
return null;
}
// 获取共享队列,该队列在 fiber 的更新队列中共享。
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
// 检查是否是在不安全的类组件渲染阶段更新,如果是,则立即处理。
if (isUnsafeClassRenderPhaseUpdate(fiber)) {
// 这是一个不安全的渲染阶段更新,直接将更新添加到更新队列中,
// 这样我们可以在当前渲染阶段立即处理它。
const pending = sharedQueue.pending;
if (pending === null) {
// 这是第一次更新,创建一个循环列表。
update.next = update;
} else {
// 将更新插入到循环列表中。
update.next = pending.next;
pending.next = update;
}
sharedQueue.pending = update;
// 更新 childLanes,即使我们很可能已经在渲染这个 fiber。
// 这是为了向后兼容,在渲染阶段更新其他组件的情况(这种模式会伴随着一个警告)。
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
} else {
// 对于并发类组件的更新,将更新排队并返回根 fiber。
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
}
}
这里我们解释下什么叫不安全的类组件渲染阶段更新: 指的是在 React 类组件的渲染方法 (render
方法) 或生命周期方法中(如 componentWillMount
, componentWillReceiveProps
, componentWillUpdate
)进行状态更新或调度更新操作是不安全的,因为这些方法可能会在一次渲染过程中多次调用,导致难以预测的行为和性能问题。
在 React 中,状态更新通常应该在"安全的"生命周期方法中进行,比如 componentDidMount
, componentDidUpdate
,或在事件处理函数中进行。这些方法确保更新只会在某个特定的时间点发生,减少了复杂性和错误。
4.enqueueConcurrentClassUpdate
并发模式下将一个类组件的更新对象添加到其对应的 Fiber 的更新队列中
js
/**
* 并发模式下将一个类组件的更新对象添加到其对应的 Fiber 的更新队列中。
*
* @param {Fiber} fiber 当前组件的 Fiber 对象。
* @param {ClassQueue<State>} queue 类组件的更新队列。
* @param {ClassUpdate<State>} update 要添加的更新对象。
* @param {Lane} lane 表示更新的优先级。
* @returns {Fiber} 标记了更新优先级的根 Fiber。
*/
export function enqueueConcurrentClassUpdate<State>(
fiber: Fiber,
queue: ClassQueue<State>,
update: ClassUpdate<State>,
lane: Lane,
) {
const interleaved = queue.interleaved;
// 如果 interleaved 为空,这表示这是第一个更新对象
if (interleaved === null) {
// 创建一个循环链表,即自己指向自己
update.next = update;
// 在当前渲染结束时,这个队列的 interleaved 更新将被转移到 pending 队列
pushConcurrentUpdateQueue(queue);
} else {
// 否则,将更新对象插入到循环链表中
update.next = interleaved.next;
interleaved.next = update;
}
// 更新队列的 interleaved 指向最新的更新对象
queue.interleaved = update;
// 标记从当前 Fiber 到根 Fiber 的更新优先级,并返回根 Fiber
return markUpdateLaneFromFiberToRoot(fiber, lane);
}
添加到队列中
js
export function pushConcurrentUpdateQueue(
queue: HookQueue<any, any> | ClassQueue<any>,
) {
if (concurrentQueues === null) {
concurrentQueues = [queue];
} else {
concurrentQueues.push(queue);
}
}
5.为什么设计为一个环状链表?
主要原因是为了方便管理和处理更新对象,我们还可以根据指针快速获取首尾节点
- 循环更新 :环状链表可以实现循环更新,即每个更新对象的
next
指针指向下一个更新对象,最后一个更新对象的next
指针指向第一个更新对象,形成一个闭环。这样,在处理更新时可以不断循环链表,依次处理每个更新对象。 - 避免内存泄漏:环状链表的闭环结构可以避免因为更新对象没有被正确清理而导致的内存泄漏。在执行完一轮更新后,如果链表中仍有更新对象,可以确保这些更新对象不会被遗漏。
- 简化逻辑:环状链表的结构相对简单,处理起来更加方便。在添加新的更新对象时,只需将新对象插入到链表中即可,无需考虑链表的头尾问题。
- 高效处理:通过循环链表可以高效地处理更新对象。在处理完所有更新对象后,如果有新的更新对象添加进来,可以直接插入到链表中,无需重新构建整个链表。
四、newState的构造
1.发起调度
在 enqueueUpdate
末尾,执行了 scheduleUpdateOnFiber
函数,该方法最终会调用 ensureRootIsScheduled
函数来调度react的应用根节点。
当进入 performConcurrentWorkOnRoot
函数时,就代表进入了 reconciler
阶段,也就是我们说的 render
阶段。render
阶段是一个自顶向下再自底向上的过程,从react的应用根节点开始一直向下遍历,再从底部节点往上回归,这就是render
阶段的节点遍历过程。
这里我们需要知道的是,在render
阶段自顶向下遍历的过程中,如果遇到组件类型的Fiber节点,我们会执行 processUpdateQueue
函数,这个函数主要负责的是组件更新时 state 的计算,具体的调度流程我们在后续的文章中会详细解释,这里我们主要讲解更新队列准备好之后,如何处理更新队列
2.processUpdateQueue
- 初始化:获取更新队列,重置标志。
- 处理待处理的更新:将挂起的更新转移到基本更新队列。
- 处理基本更新队列:遍历基本更新队列,计算新的状态,并将任何必要的回调排入队列。
- 清理和恢复:清理开发环境中的状态。
js
/**
* 处理组件的更新队列,将队列中的更新应用到组件的状态上。
*
* @param {InternalInstance} internalInstance 组件的内部实例对象,包含更新队列等信息。
* @param {any} inst 组件实例。
* @param {any} props 组件的当前属性。
* @param {any} maskedLegacyContext 组件的上下文。
*/
function processUpdateQueue(
internalInstance: InternalInstance,
inst: any,
props: any,
maskedLegacyContext: any,
): void {
// 检查内部实例的更新队列是否存在且不为空
if (internalInstance.queue !== null && internalInstance.queue.length > 0) {
// 保存旧的更新队列和替换标志
const oldQueue = internalInstance.queue;
const oldReplace = internalInstance.replace;
// 清空内部实例的更新队列和替换标志
internalInstance.queue = null;
internalInstance.replace = false;
// 如果替换标志为真且队列长度为 1,则直接替换组件的状态
if (oldReplace && oldQueue.length === 1) {
inst.state = oldQueue[0];
} else {
// 否则,合并队列中的更新到组件的当前状态
// 如果替换标志为真,则使用队列的第一个更新对象作为初始状态
// 否则,使用组件的当前状态作为初始状态
let nextState = oldReplace ? oldQueue[0] : inst.state;
let dontMutate = true; // 标志是否需要创建新对象
// 遍历更新队列,从第二个或第一个更新对象开始处理(取决于替换标志)
for (let i = oldReplace ? 1 : 0; i < oldQueue.length; i++) {
const partial = oldQueue[i];
// 如果更新对象是一个函数,则调用它并传入当前状态、属性和上下文,获取部分状态更新
const partialState =
typeof partial === 'function'
? partial.call(inst, nextState, props, maskedLegacyContext)
: partial;
// 如果部分状态更新不为空,则合并到下一个状态中
if (partialState != null) {
if (dontMutate) {
dontMutate = false;
// 创建新对象,合并部分状态更新
nextState = assign({}, nextState, partialState);
} else {
// 直接合并部分状态更新到下一个状态中
assign(nextState, partialState);
}
}
}
// 将合并后的状态赋值给组件的状态
inst.state = nextState;
}
} else {
// 如果更新队列为空,则清空内部实例的更新队列
internalInstance.queue = null;
}
}
-
检查更新队列:
- 检查
internalInstance.queue
是否存在且不为空。如果更新队列为空,则不进行任何操作。
- 检查
-
保存旧队列和替换标志:
- 保存旧的更新队列
oldQueue
和替换标志oldReplace
。
- 保存旧的更新队列
-
清空更新队列和替换标志:
- 将
internalInstance.queue
设为null
,将internalInstance.replace
设为false
。
- 将
-
处理单个替换更新:
- 如果替换标志为真且队列长度为 1,则直接用队列中的第一个更新对象替换组件的状态。
-
合并多个更新对象:
- 否则,遍历更新队列中的更新对象,并合并这些更新到组件的当前状态中。
- 如果
oldReplace
为真,则nextState
初始化为队列的第一个更新对象;否则,初始化为组件的当前状态。 - 使用
dontMutate
标志来决定是否创建新对象以避免直接修改当前状态。
-
合并部分状态更新:
- 对于每个更新对象,如果是函数,则调用该函数并传入当前状态、属性和上下文,获取部分状态更新。
- 如果部分状态更新不为空,则将其合并到
nextState
中。 - 如果
dontMutate
为真,则创建新对象并合并部分状态更新;否则,直接合并部分状态更新到现有的nextState
中。
-
更新组件状态:
- 最后,将合并后的状态赋值给组件的状态
inst.state
。
- 最后,将合并后的状态赋值给组件的状态
阶段1:初始化
这个阶段主要是进行初始化操作,包括重置一些状态变量,获取更新队列等。
typescript
export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes,
): void {
// 获取当前 fiber 的更新队列
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
// 重置强制更新标志
hasForceUpdate = false;
// 开发环境中记录当前处理的队列
if (__DEV__) {
currentlyProcessingQueue = queue.shared;
}
// 获取基本更新队列的第一个和最后一个更新
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
阶段2:处理待处理的更新
基本队列(Base Queue)
基本队列是指当前 fiber 节点上的更新队列。这些更新是已经被调度且正在处理中或等待处理的更新。基本队列中的更新是按顺序排列的,每个更新包含新的状态或属性变更信息。
当前队列(Current Queue)
当前队列指的是 fiber 节点的备用 fiber(alternate fiber)上的更新队列。在 React 中,每个 fiber 节点都有一个当前 fiber 和一个备用 fiber,它们在更新时交替使用。当前队列记录了备用 fiber 上的更新情况,与基本队列进行同步。
待处理更新队列(Pending Updates)
待处理更新是指那些尚未添加到基本队列中的更新。它们通常是由于用户交互或其他异步操作引发的更新。这些更新会被暂时存储在共享队列(shared queue)中,直到下一次渲染周期将它们转移到基本队列中进行处理。
关系和流程
- 待处理更新:在某个时刻,有新的更新被调度,这些更新会先进入待处理更新队列(挂起队列),这是一个循环链表,方便在任意时间点添加新的更新。
- 基本队列:在处理更新的过程中,待处理更新会被转移到基本队列。基本队列中的更新是按顺序排列的,它们将按顺序被应用到组件的状态或属性上。
- 当前队列:为了确保更新过程的一致性,React 维护了一个当前队列。这是备用 fiber 上的更新队列。在更新过程中,基本队列中的更新也会被同步到当前队列。
具体流程
在阶段二的处理过程中,流程如下:
- 检查待处理更新:首先检查共享队列中是否有待处理的更新。如果有,将它们从共享队列中取出。
- 转换为非循环队列:待处理更新队列是一个循环链表。需要将它转换为非循环链表,以便可以将它们添加到基本队列中。
- 附加到基本队列:如果基本队列为空,则将待处理更新作为基本队列的第一个和最后一个更新。否则,将待处理更新添加到基本队列的末尾。
- 同步到当前队列:如果存在备用 fiber,则需要将待处理更新同步到当前队列。这一步是为了确保在更新过程中,当前 fiber 和备用 fiber 的更新队列保持一致。
js
// 检查是否有待处理的更新,如果有,将它们转移到基本队列中
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;
// 待处理队列是一个循环队列,断开第一个和最后一个更新之间的指针,使其非循环
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
// 将待处理更新附加到基本队列
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;
// 如果有当前队列,并且它与基本队列不同,则需要将更新转移到该队列
const current = workInProgress.alternate;
if (current !== null) {
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}
阶段3:处理基本更新队列
这个阶段会遍历基本更新队列,计算新的状态,并将任何必要的回调排入队列。
- 初始化新状态和新的更新队列:准备存储新状态和新的更新队列信息。
- 遍历基本更新队列:从第一个基本更新开始,遍历整个更新队列。
- 检查更新的优先级:检查每个更新的优先级是否足够高以在当前渲染过程中处理。
- 处理有足够优先级的更新:计算新的状态,并将其添加到新的基本更新队列中。
- 处理回调:将包含回调的更新添加到回调队列中。
- 更新基本状态和基本更新队列:遍历完所有更新后,更新基本状态和基本更新队列。
- 处理交错的更新:检查并更新交错的更新和剩余的优先级。
- 更新跳过的优先级:更新跳过的优先级和 fiber 节点的状态。
js
// 处理基本更新队列,计算新的状态
if (firstBaseUpdate !== null) {
let newState = queue.baseState;
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不足,跳过此更新
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newLanes = mergeLanes(newLanes, updateLane);
} else {
// 优先级足够,处理更新
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
lane: NoLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// 处理此更新,计算新的状态
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (callback !== null && update.lane !== NoLane) {
workInProgress.flags |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
// 处理交错更新
const lastInterleaved = queue.shared.interleaved;
if (lastInterleaved !== null) {
let interleaved = lastInterleaved;
do {
newLanes = mergeLanes(newLanes, interleaved.lane);
interleaved = ((interleaved: any).next: Update<State>);
} while (interleaved !== lastInterleaved);
} else if (firstBaseUpdate === null) {
queue.shared.lanes = NoLanes;
}
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
初始化新状态和新的更新队列
在开始遍历基本更新队列之前,初始化一些变量来存储新状态和新的更新队列信息。
typescript
let newState = queue.baseState;
let newLanes = NoLanes;
let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
遍历基本更新队列
从第一个基本更新开始遍历整个更新队列,逐个处理每个更新。
typescript
let update = firstBaseUpdate;
do {
检查更新的优先级
对于每个更新,检查其优先级是否足够高以在当前渲染过程中处理。如果优先级不足,则跳过该更新,并将其添加到新的基本更新队列中,注意这里添加到基本更新队列中即是currentFiber中,currentFiber在打断之后不会重构,只有workInProgress tree会重构到低优先级更新之前的基本更新状态,这是双缓冲树机制,我们会在后面文章中介绍,这里主要理解更新调度机制和更新队列的处理。
typescript
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
// 优先级不足,跳过此更新
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// 更新队列中剩余的优先级
newLanes = mergeLanes(newLanes, updateLane);
} else {
处理有足够优先级的更新
如果更新的优先级足够高,则处理该更新,计算新的状态,并将其添加到新的基本更新队列中。
typescript
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
lane: NoLane,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
处理回调
如果更新包含回调,则将其添加到回调队列中。
typescript
const callback = update.callback;
if (callback !== null && update.lane !== NoLane) {
workInProgress.flags |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
// 从挂起队列中获取新的更新并继续处理
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
更新基本状态和基本更新队列
遍历完所有更新后,更新基本状态和基本更新队列。
typescript
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = newBaseState;
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
处理交错的更新
检查是否有交错的更新(在不同渲染阶段产生的更新),并更新剩余的优先级。
typescript
const lastInterleaved = queue.shared.interleaved;
if (lastInterleaved !== null) {
let interleaved = lastInterleaved;
do {
newLanes = mergeLanes(newLanes, interleaved.lane);
interleaved = interleaved.next;
} while (interleaved !== lastInterleaved);
} else if (firstBaseUpdate === null) {
queue.shared.lanes = NoLanes;
}
更新跳过的优先级
更新跳过的优先级和 fiber 节点的状态。
typescript
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
这便是完整的更新调度机制&更新队列的处理。
推荐阅读
开源电子书
工程化系列
本系列是一个从0到1的实现过程,如果您有耐心跟着实现,您可以实现一个完整的react18 + ts5 + webpack5 + 代码质量&代码风格检测&自动修复 + storybook8 + rollup + git action
实现的一个完整的组件库模板项目。如果您不打算自己配置,也可以直接clone组件库仓库切换到rollup_comp
分支即是完整的项目,当前实现已经足够个人使用,后续我们会新增webpack5优化、按需加载组件、实现一些常见的组件封装:包括但不限于拖拽排序、瀑布流、穿梭框、弹窗等
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程一】
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程二】
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程三】
- 手把手带你搭建前端项目:react18、ts5、lint四剑客、webpack、storybook【保姆级教程四】
- 前端三大包管理器你知道多少?npm、yarn、pnpm
- 前端怎么可以不会GitHub Action一键部署?
面试手写系列
react实现原理系列
- 【react18原理探究实践】使用babel手搓探索下jsx的原理
- 【react18原理探究实践】上手调试源码探究jsx原理
- 【react18原理探究实践】图解react几个核心包之间的关联
- 【react18原理探究实践】react启动流程,其实就是创建三大全局对象
- 【react18原理探究实践】JS中的位运算&react中的lane模型
其他
🍋 写在最后
如果您看到这里了,并且觉得这篇文章对您有所帮助,希望您能够点赞👍和收藏⭐支持一下作者🙇🙇🙇,感谢🍺🍺!如果文中有任何不准确之处,也欢迎您指正,共同进步。感谢您的阅读,期待您的点赞👍和收藏⭐!
感兴趣的同学可以关注下我的公众号ObjectX前端实验室
🌟 少走弯路 | ObjectX前端实验室 🛠️「精选资源|实战经验|技术洞见」