enqueueUpdate
从设计者的角度,根据 单一职责原则 和 开闭口原则 需要有与函数体解耦的数据结构来告诉 React 应该怎么操作 fiber 。而不是初次渲染写一套逻辑,第二次渲染写一套逻辑。因此, fiber 上有了更新队列 UpdateQueue
和 更新链表 Update
结构
如果查看一下相关的定义就会发现,更新队列 updateQueue
是多个更新组成的链表结构,而 update
的更新也是一个链表,至于为什么是这样设计,试想在一个 Class Component 的更新函数中连续执行了 3 次 setState ,与其将其作为 3 个更新挂载到组件上,不如提供一种更小粒度的控制方式。一句话概括就是, setState 级别的小更新合并成一个状态更新,组件中的多个状态更新在组件的更新队列中合并,就能够计算出组件的新状态 newState
。
对于初次渲染而言,只需要在第一个 fiber 上,挂载一个 update
标识这是一个初次渲染的 fiber 即可。
// 更新根节点
export function ScheduleRootUpdate (
current: Fiber,
element: ReactElement,
expirationTime: number,
suspenseConfig: SuspenseConfig | null,
callback?: Function
) {
// 创建一个 update 实例
const update = createUpdate(expirationTime, suspenseConfig)
// 对于作用在根节点上的 react element
update.payload = {
element
}
// 将 update 挂载到根 fiber 的 updateQueue 属性上
enqueueUpdate(
current,
update
)
ScheduleWork(
current,
expirationTime
)
}
Fiber
作为整个 Fiber 架构 中最核心的设计, Fiber 被设计成了链表结构。
- child 指向当前节点的第一个子元素
- return 指向当前节点的父元素
- sibling 指向同级的下一个兄弟节点
如果是 React16 之前的树状结构,就需要通过 DFS 深度遍历来查找每一个节点。而现在只需要将指针按照 child → sibling → return 的优先级移动,就可以处理所有的节点
这样设计还有一个好处就是在 React 工作的时候只需要使用一个全局变量作为指针在链表中不断移动,如果出现用户输入或其他优先级更高的任务就可以 暂停 当前工作,其他任务结束后只需要根据指针的位置继续向下移动就可以继续之前的工作。指针移动的规律可以归纳为 自顶向下,从左到右 。
康康 fiber 的基本结构
其中
- tag fiber 的类型 ,例如函数组件,类组件,原生组件, Portal 等。
- type React 元素 类型 详见上方 createElement。
- alternate 代表双向缓冲对象(看后面)。
- effectTag 代表这个 fiber 在下一次渲染中将会被如何处理。例如只需要插入,那么这个值中会包含 Placement ,如果需要被删除,那么将会包含 Deletion 。
- expirationTime 过期时间,过期时间越靠前,就代表这个 fiber 的优先级越高。
- firstEffect 和 lastEffect 的类型都和 fiber 一样,同样是链表结构,通过 nextEffect 来连接。代表着即将更新的 fiber 状态
- memorizeState 和 memorizeProps 代表在上次渲染中组件的 props 和 state 。如果成功更新,那么新的 pendingProps 和 newState 将会替代这两个变量的值
- ref 引用标识
- stateNode 代表这个 fiber 节点对应的真实状态
- 对于原生组件,这个值指向一个 dom 节点(虽然已经被创建了,但不代表就被插入了 document )
- 对于类组件,这个值指向对应的类实例
- 对于函数组件,这个值指向 Null
- 对于 RootFiber,这个值指向 FiberRoot (如图)
接下来是初次渲染的几个核心步骤,因为是初次渲染,核心任务就是将首屏元素渲染到页面上,所以这个过程将会是同步的。
PrepareFreshStack
因为笔者是土货没学过英语,百度了下发现是 准备干净的栈 的意思。结合了下流程,可以看出这一步的作用是在真正工作之前做一些准备,例如初始化一些变量,放弃之前未完成的工作,以及最重要的—— 创建双向缓冲变量 WorkInProgress
let workInProgress: Fiber | null = null
...
export function prepareFreshStack (
root: FiberRoot,
expirationTime: number
) {
// 重置根节点的 finishWork
root.finishedWork = null
root.finishedExpirationTime = ExpirationTime.NoWork
...
if (workInProgress !== null) {
// 如果已经存在了 WIP,说明存在未完成的任务
// 向上找到它的 root fiber
let interruptedWork = workInProgress.return
while (interruptedWork !== null) {
// unwindInterruptedWork // 抹去未完成的任务
unwindInterruptedWork(interruptedWork)
interruptedWork = interruptedWork.return
}
}
workInProgressRoot = root
// 创建双向缓冲对象
workInProgress = createWorkInProgress(root.current, null, expirationTime)
renderExpirationTime = expirationTime
workInProgressRootExitStatus = RootExitStatus.RootImcomplete
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论