学习串联渲染链路你能学到什么?
- React 16 在所有情况下都是异步渲染的吗?
- Fiber 架构中的“可中断”“可恢复”到底是如何实现的?
- Fiber 树和传统虚拟 DOM 树有何不同?
- 优先级调度又是如何实现的?
ReactDOM.render 调用栈
ReactDOM.render 调用栈大致可以拆分成如下三个阶段:
- 初始化阶段
- render阶段
- commit阶段
初始化阶段
完成Fiber树中基本实体的创建,哪什么是Fiber,它是干啥的,基本实体又是什么?
Fiber是什么?
Fiber是对React核心算法的重构。
Fiber的作用?
- 把可中断的工作拆分小任务
- 对正在操作的工作调整优先次序、重做、复用
- 在父子任务之间切换,以支持React执行过程中的布局刷新
- 支持render返回多个元素
基本实体是什么?
技基础实体想要了解他,来看一段源码
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) { // container 对应的是我们传入的真实 DOM 对象 var root = container._reactRootContainer; // 初始化 fiberRoot 对象 var fiberRoot; // DOM 对象本身不存在 _reactRootContainer 属性,因此 root 为空 if (!root) { // 若 root 为空,则初始化 _reactRootContainer,并将其值赋值给 root root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate); // legacyCreateRootFromDOMContainer 创建出的对象会有一个 _internalRoot 属性,将其赋值给 fiberRoot fiberRoot = root._internalRoot; // 这里处理的是 ReactDOM.render 入参中的回调函数,你了解即可 if (typeof callback === 'function') { var originalCallback = callback; callback = function () { var instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // Initial mount should not be batched. // 进入 unbatchedUpdates 方法 unbatchedUpdates(function () { updateContainer(children, fiberRoot, parentComponent, callback); }); } else { // else 逻辑处理的是非首次渲染的情况(即更新),其逻辑除了跳过了初始化工作,与楼上基本一致 fiberRoot = root._internalRoot; if (typeof callback === 'function') { var _originalCallback = callback; callback = function () { var instance = getPublicRootInstance(fiberRoot); _originalCallback.call(instance); }; } // Update updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }
在这段源码中主要的操作就是赋值fiberRoot对象,这个对象是通过root._internalRoot赋值,本质上这个对象是一个FiberRootNode对象,其中包含一个current对象,这个对象是FiberNode实例。而 FiberNode,正是 Fiber 节点对应的对象类型。current 对象是一个 Fiber 节点,不仅如此,它还是当前 Fiber 树的头部节点。其中,fiberRoot 的关联对象是真实 DOM 的容器节点;而 rootFiber 则作为虚拟 DOM 的根节点存在。这两个节点,将是后续整棵 Fiber 树构建的起点。
整个初始化的工作过程都是在为后续的 render 阶段做准备。现在,我们的 Fiber Tree 还处在只有根节点的起始状态。接下来,我们就要进入到最最关键的 render 阶段里去,一起去看看这棵树是怎么一点点开枝散叶的吧!
render阶段
scheduleUpdateOnFiber 方法的作用是调度更新,在由 ReactDOM.render 发起的首屏渲染这个场景下,它触发的就是 performSyncWorkOnRoot。performSyncWorkOnRoot 开启的正是我们反复强调的 render 阶段;finishSyncRoot标志着render方法的结束。在这个过程中,穿插了大量了beginWork、completeWork调用(这两个方法串联起来就是一个模拟递归的过程)。这个两个方法就是render的工作内容。
在React15中调和是一个递归的过程。在Fiber架构下,虽然不依赖递归,但是ReactDOM.render模式下,他整体是一个同步过程,是一个优先深度遍历。在这个过程中beginWork是负责创建Fiber节点,completeWork负责将Fiber节点映射为真实DOM。
接下来来看看这个过程有那些比较重要的调用
performSyncWorkOnRoot:开始render阶段
renderRootSync:准备工作
prepareFreshStack:重置一个新的堆栈环境
这里比较重要的是调用了createWorkInProgress这个函数,我们来看看这个函数干了什么
createWorkInProgress
workLoopSync:通过 while 循环反复判断 workInProgress 是否为空,并在不为空的情况下针对它执行 performUnitOfWork 函数。
performUnitOfWork:触发对beginWork的调用。performUnitOfWork,其主要工作是“通过调用 beginWork,来实现新 Fiber 节点的创建”;它还有一个次要工作,就是把新创建的这个 Fiber 节点的值更新到 workInProgress 变量里去。
beginWork:创建Fiber节点
beginWork 的核心逻辑是根据 fiber 节点(workInProgress)的 tag 属性的不同,调用不同的节点创建函数。在beginWork后面还有很多逻辑,感兴趣的同学可以深挖,借鉴网上的一张图如下:
Fiber 节点间是如何连接
不同的 Fiber 节点之间,将通过 child、return、sibling 这 3 个属性建立关系,其中 child、return 记录的是父子节点关系,而 sibling 记录的则是兄弟节点关系。FiberNode 实例中,return 指向的是当前 Fiber 节点的父节点,而 sibling 指向的是当前节点的第 1 个兄弟节点。