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了。

相关推荐
程序员老赵1 小时前
Docker 部署 ZLMediaKit:轻松搭建高性能流媒体服务平台
前端·javascript·后端
拾年2751 小时前
前后端分离开发,别再"傻等"后端接口了——聊聊前端接口工程
前端·javascript·react.js
SendTomo1 小时前
SendTomo稳定传输三大核心策略
javascript·网络协议·webrtc·html5·p2p
天才熊猫君1 小时前
从“改自己”到“改布局”——拖拽调整的通用设计思路
前端·javascript
weixin_BYSJ19871 小时前
springboot小区物业管理小程序---附源码45555
java·javascript·spring boot·python·django·flask·php
默_笙1 小时前
🍳 受控组件和非受控组件,我纠结了一整天,最后用"房东和租客"讲明白了
前端·javascript
半个落月2 小时前
React 受控组件与非受控组件详解:从输入框到表单校验
前端·react.js
Hilaku2 小时前
高级前端如何优雅地拒绝不合理的产品需求?
前端·javascript·程序员
独立开阀者_FwtCoder2 小时前
这次更新:换新主题,同时把好计划分享出去
前端·javascript·vue.js
禁止摆烂_才浅2 小时前
JavaScript WebAPI(进阶)高频面试题
前端·javascript·面试