react中render阶段做了什么

首先说明一个概念:

render阶段对应的是Reconciler(协调器),

commit阶段对应的的是Renderer(渲染器)

render阶段开始于performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用。这取决于本次更新是同步更新还是异步更新。

javascript 复制代码
// performSyncWorkOnRoot会调用该方法
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

我们知道Fiber Reconciler是从Stack Reconciler重构而来,通过遍历的方式实现可中断的递归,所以performUnitOfWork的工作可以分为两部分:"递"和"归"。

"递"阶段

首先从rootFiber开始向下深度优先遍历,为遍历到的每个Fiber节点调用beginWork 方法。

该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。

当遍历到叶子节点(即没有子组件的组件)时就会进入"归"阶段。
effectTag:

也会做一些标记,这个标记主要和元素的位置有关系。

javascript 复制代码
// DOM需要插入到页面中
export const Placement = /*                */ 0b00000000000010;
// DOM需要更新
export const Update = /*                   */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /*       */ 0b00000000000110;
// DOM需要删除
export const Deletion = /*                 */ 0b00000000001000;

"归"阶段:

  • 为Fiber节点生成对应的DOM节点
  • 将子孙DOM节点插入刚生成的DOM节点中
javascript 复制代码
// mount的情况

// ...省略服务端渲染相关逻辑

const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(
  type,
  newProps,
  rootContainerInstance,
  currentHostContext,
  workInProgress
);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;

// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
  finalizeInitialChildren(
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext
  )
) {
  markUpdate(workInProgress);
}

那么commit阶段是如何通过一次插入DOM操作(对应一个Placement effectTag)将整棵DOM树插入页面的呢?

原因就在于completeWork中的appendAllChildren方法。

由于completeWork属于"归"阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下。那么当"归"到rootFiber时,我们已经有一个构建好的离屏DOM树。
EffectList:

至此render阶段的绝大部分工作就完成了。

还有一个问题:作为DOM操作的依据,commit阶段需要找到所有有effectTag的Fiber节点并依次执行effectTag对应操作。难道需要在commit阶段再遍历一次Fiber树寻找effectTag !== null的Fiber节点么?

这显然是很低效的。

为了解决这个问题,在completeWork的上层函数completeUnitOfWork中,每个执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中。

effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。

类似appendAllChildren,在"归"阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表。

javascript 复制代码
                      nextEffect         nextEffect
rootFiber.firstEffect -----------> fiber -----------> fiber

这样,在commit阶段只需要遍历effectList就能执行所有effect了。

相关推荐
枫荷2 分钟前
简单介绍serviceWorker使用场景
前端·javascript
夏天想11 分钟前
移动端项目框架推荐
前端·javascript·vue.js
zhanle_huang22 分钟前
web前端结合Microsoft Office Online 在线预览,vue实现(PPT、Word、Excel、PDF等)
前端·javascript·vue.js
Jimmy28 分钟前
构建健壮 React 应用的 5 个重要实践
前端·javascript·react.js
典学长编程1 小时前
前端开发(HTML,CSS,VUE,JS)从入门到精通!第六天(Vue)
javascript·css·vue.js·vue·html
鲸落落丶1 小时前
前端三大核心要素以及前后端通讯
javascript·css·html·jquery
OEC小胖胖1 小时前
幕后英雄 —— Background Scripts (Service Worker)
开发语言·前端·javascript·浏览器·web·扩展
Monika Zhang1 小时前
React框架深度解析与主流前端框架对比
前端·react.js·前端框架
胡斌附体1 小时前
小程序省市级联组件使用
前端·javascript·小程序·uniapp·picker级联组件
江城开朗的豌豆1 小时前
Redux三剑客:揭秘reducer这个'状态改造师'的魔法
前端·javascript·react.js