返回介绍

enqueueUpdate

发布于 2025-01-10 12:49:04 字数 4067 浏览 0 评论 0 收藏 0

从设计者的角度,根据 单一职责原则开闭口原则 需要有与函数体解耦的数据结构来告诉 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 的优先级移动,就可以处理所有的节点

http://www.wenjiangs.com/wp-content/uploads/2024/docimg16/1000-s1fg3rpu3rx.png

这样设计还有一个好处就是在 React 工作的时候只需要使用一个全局变量作为指针在链表中不断移动,如果出现用户输入或其他优先级更高的任务就可以 暂停 当前工作,其他任务结束后只需要根据指针的位置继续向下移动就可以继续之前的工作。指针移动的规律可以归纳为 自顶向下,从左到右 。

康康 fiber 的基本结构

http://www.wenjiangs.com/wp-content/uploads/2024/docimg16/1001-q15vgi3uetf.png

其中

  • tag fiber 的类型 ,例如函数组件,类组件,原生组件, Portal 等。
  • type React 元素 类型 详见上方 createElement。
  • alternate 代表双向缓冲对象(看后面)。
  • effectTag 代表这个 fiber 在下一次渲染中将会被如何处理。例如只需要插入,那么这个值中会包含 Placement ,如果需要被删除,那么将会包含 Deletion 。
  • expirationTime 过期时间,过期时间越靠前,就代表这个 fiber 的优先级越高。
  • firstEffectlastEffect 的类型都和 fiber 一样,同样是链表结构,通过 nextEffect 来连接。代表着即将更新的 fiber 状态
  • memorizeStatememorizeProps 代表在上次渲染中组件的 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文