第三章render函数-提交阶段:3.4 流程概览

commitRoot方法是commit阶段工作的起点。fiberRootNode会作为传参,commitRoot(root)。

rootFiber.firstEffect上保存了一条需要执行副作用的Fiber节点的单向链表effectList,这些Fiber节点的updateQueue中保存了变化的props,这些副作用对应的DOM操作在commit阶段执行。除此之外,一些生命周期钩子(比如componentDidXXX)、hook(比如useEffect)需要在commit阶段执行。

commit阶段的主要工作分为三部分,

  • before mutation 阶段(执行DOM操作前)
  • mutation 阶段(执行DOM操作)
  • layout 阶段(执行DOM操作后)

在before mutation阶段之前和layout阶段之后还有一些额外工作,涉及到比如useEffect的触发、优先级相关的重置、ref的绑定/解绑。

before mutation 之前

commitRootImpl方法中直到第一句if (firstEffect !== null)之前属于before mutation之前。before mutation之前主要做一些变量赋值,状态重置的工作。我们只需要关注最后赋值的firstEffect,在commit的三个子阶段都会用到他。

伪代码

ini 复制代码
do {
  // 触发useEffect回调与其他同步任务。由于这些任务可能触发新的渲染,所以这里要一直遍历执行直到没有任务
  flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);

// root指 fiberRootNode
// root.finishedWork指当前应用的rootFiber
const finishedWork = root.finishedWork;

// 凡是变量名带lane的都是优先级相关
const lanes = root.finishedLanes;
if (finishedWork === null) {
  return null;
}
root.finishedWork = null;
root.finishedLanes = NoLanes;

// 重置Scheduler绑定的回调函数
root.callbackNode = null;
root.callbackId = NoLanes;

let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
// 重置优先级相关变量
markRootFinished(root, remainingLanes);

// 清除已完成的discrete updates,例如:用户鼠标点击触发的更新。
if (rootsWithPendingDiscreteUpdates !== null) {
  if (
    !hasDiscreteLanes(remainingLanes) &&
    rootsWithPendingDiscreteUpdates.has(root)
  ) {
    rootsWithPendingDiscreteUpdates.delete(root);
  }
}

// 重置全局变量
if (root === workInProgressRoot) {
  workInProgressRoot = null;
  workInProgress = null;
  workInProgressRootRenderLanes = NoLanes;
} else {
}

// 将effectList赋值给firstEffect
// 由于每个fiber的effectList只包含他的子孙节点
// 所以根节点如果有effectTag则不会被包含进来
// 所以这里将有effectTag的根节点插入到effectList尾部
// 这样才能保证有effect的fiber都在effectList中
let firstEffect;
if (finishedWork.effectTag > PerformedWork) {
  if (finishedWork.lastEffect !== null) {
    finishedWork.lastEffect.nextEffect = finishedWork;
    firstEffect = finishedWork.firstEffect;
  } else {
    firstEffect = finishedWork;
  }
} else {
  // 根节点没有effectTag
  firstEffect = finishedWork.firstEffect;
}

layout 之后

主要包括三点内容:

  • useEffect相关的处理。
  • 性能追踪相关,源码里有很多和interaction相关的变量。他们都和追踪React渲染时间、性能相关,在Profiler API和DevTools中使用。
  • 在commit阶段会触发一些生命周期钩子(如 componentDidXXX)和hook(如useLayoutEffect、useEffect)

伪代码

scss 复制代码
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

// useEffect相关
if (rootDoesHavePassiveEffects) {
  rootDoesHavePassiveEffects = false;
  rootWithPendingPassiveEffects = root;
  pendingPassiveEffectsLanes = lanes;
  pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
}

// 性能优化相关
if (remainingLanes !== NoLanes) {
  if (enableSchedulerTracing) {
    // ...
  }
} else {
  // ...
}

// 性能优化相关
if (enableSchedulerTracing) {
  if (!rootDidHavePassiveEffects) {
    // ...
  }
}

// ...检测无限循环的同步任务
if (remainingLanes === SyncLane) {
  // ...
}

// 在离开commitRoot函数前调用,触发一次新的调度,确保任何附加的任务被调度
ensureRootIsScheduled(root, now());

// ...处理未捕获错误及老版本遗留的边界问题

// 执行同步任务,这样同步任务不需要等到下次事件循环再执行
// 比如在 componentDidMount 中执行 setState 创建的更新会在这里被同步执行
// 或useLayoutEffect
flushSyncCallbackQueue();

return null;

参考链接

关于作者

作者:Wandra

内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。

专栏:欢迎关注🌹

本专栏致力于分析热门项目,如果本文对你有帮助的话,欢迎点赞或关注。

相关推荐
有梦想的咕噜5 分钟前
Electron 是一个用于构建跨平台桌面应用程序的开源框架
前端·javascript·electron
yqcoder8 分钟前
electron 监听窗口高端变化
前端·javascript·vue.js
Python私教24 分钟前
Flutter主题最佳实践
前端·javascript·flutter
GDAL42 分钟前
HTML入门教程7:HTML样式
前端·html
生命几十年3万天1 小时前
解决edge浏览器无法同步问题
前端·edge
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的校园美食分享平台
java·开发语言·前端·vue.js·spring boot·java-ee·美食
API199701081102 小时前
京东平台接口技术详解及示例代码
开发语言·前端·python
前端热爱者2 小时前
axios post请求body为字符串时的解决方法
开发语言·前端·javascript
Monly212 小时前
JS:JSON操作
前端·javascript·json
zerotower3 小时前
nextjs 构建自己的react组件库(一) - 项目的初始化配置和部署
前端·next.js