6

React 源码解析系列 - React 的 render 阶段(一):基本流程介绍

 2 years ago
source link: https://segmentfault.com/a/1190000040437854
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

React 的工作流程

React 的工作流程主要分为 render 和 commit 两个阶段:

  • render 阶段根据 JSX 转化成的 ReactElement (更准确地来说,jsx 经 babel 转化后是 React.createElement() 的代码段,在 render 阶段该代码段被执行后便生成了对应的 ReactElement 对象)来创建 Fiber 树;React 采用“双缓存”的思想,因此同一时刻维持有两棵 Fiber 树 ,一颗是 current,对应浏览器当前已渲染的 DOM 树,而另一棵则是 workInProgress,是在初始化时或者组件状态更新后由 reconciler 创建的一个工作副本。
  • commit 阶段指的是把 workInProgress 渲染到页面上 ,当然这个过程并不会是全量更新,而是根据创建 workInProgress 时打的一些“标记”(effectTag),来确定在某个 DOM 节点上具体做什么操作,比如更新文本、删除节点等,以尽量小的代价来在 DOM 上还原 workInProgress ;workInProgress 会在 commit 后被置为 current。

render 阶段的入口

render 阶段开始于 performSyncWorkOnRootperformConcurrentWorkOnRoot 方法的调用,这两个方法的差异在于一个是同步,而另一个是异步(concurrent)的。

这两个方法分别会调用下面两个方法—— workLoopSync 和 workLoopConcurrent:

function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

这两个方法的差别在于是否调用 shouldYield() 来判断当前浏览器帧有没有空余时间,对于 concurrent 模式来说,如果没有空余时间就会退出当前循环了;可以看到这两个方法最终都调用了 performUnitOfWork

通过方法中的 while 循环,Fiber Reconciler 将完成对虚拟 DOM 的一个深度遍历,以及完成对整棵 workInProgress Fiber Tree 的创建。

performUnitOfWork

performUnitOfWork 方法会创建下一个 Fiber 节点 ,并将其与 workInProgress 所指向的 FiberNode 进行连接(workInProgress相当于是当前 FiberTree 上最末的一个 FiberNode,与其相连则表示将新的 FiberNode 挂在 FiberTree 上),完成后将 workInProgress 这 FiberTree 的指针赋值为刚创建的 FiberNode,再进行下一轮的循环。

Fiber Reconciler 由于是从 Stack Reconciler 重构来的,它实际上模拟了“递归”的执行过程,这实际上也是一个类似于“深度优先遍历”的过程。

render 的“递”阶段 —— beginWork

在“递”阶段,调用的是 beginWork 方法,在该方法中会判断当前传入的 Fiber 节点是否有对应的子节点; 如果有子节点的话 就创建子 Fiber 节点并返回,这样在 performUnitOfWork 下一轮的循环中就会继续执行子节点的“递”阶段,否则就执行当前 Fiber 节点的“归”阶段。

render 的“归”阶段 —— completeWork

在“归”阶段,调用的是 completeUnitOfWork -> completeWork 方法;在处理完当前节点(主要是创建当前节点的 DOM ,以及收拢后代节点的 DOM 以及 effectTag)后,该方法会判断当前传入的 Fiber 节点是否有对应的兄弟节点(sibling);如果有兄弟节点的话,则返回兄弟节点,下次循环时将进入兄弟节点的“递”阶段,否则就返回父节点,下次循环时执行父节点的“归”阶段。

const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
    // If there is more work to do in this returnFiber, do that next.
    workInProgress = siblingFiber;
    return;
}
// Otherwise, return to the parent
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;

举例说明递归的过程

function App() {
    return (
        <p>
            <span>array</span>
            <span>huang</span>
        </p>
    )
}

ReactDom.render(<App />, document.getElementById('#root'));

image.png

1. rootFiber beginWork 
2. App Fiber beginWork 
3. p Fiber beginWork 
4. span Fiber beginWork 
5. span Fiber completeWork // 注意这里是直接走到 span 的“归”阶段,因为文本节点 "array" 被优化处理了
6. span Fiber beginWork 
7. span Fiber completeWork
8. p Fiber completeWork 
9. App Fiber completeWork 
10. rootFiber completeWork

至此, render 阶段全部工作完成。在 performSyncWorkOnRoot 函数中 fiberRootNode 被传递给 commitRoot 方法,开启 commit 阶段工作流程。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK