之前看很多大佬写的关于react源码的文章,但是不看下源码终归感觉不舒服,所以今天梳理了下beginwork的工作流程,我们知道现在的react是会做任务调度的,也就是会把reconcile过程分解到许多宏任务中,但那不是今天的主题。今天主要是看一下reconcile过程的一部分。
首先明确一个概念:fiber,react组件render返回的jsx最终会返回一个reactElement,浏览器环境下reactElement是对dom节点的描述,正常来说dom渲染只需要这个描述就够了,比如参考Vue的虚拟dom实现。但是react的时间分片需要任务能够随时打断继续,只靠reactElement单向引用是不够的,所以创造了fiber架构。fiber节点由reactElement生成,节点之间互相引用,形成一个fiber树,其实这东西就是一个双向链表,任意两节点之间都有指针互相指向对方,这样fiber就可以找到自己的父节点(fiber.return === parentFiber)。这样就可以很容易的对fiber节点遍历,并进行处理。
对于react来说当前正在处理的fiber节点叫做workInProgress
上面就是需要了解的前置知识,首先来看一下每个异步任务的入口,在react-reconciler/ReactFiberWorkLoop中:
很长就不贴源码了可以看到两个入参,一个是root,他是Fiber树的根节点,第二个是任务是否过期,今天要分析的代码可以不看他
代码执行到这可以看到shouldTimeSlice,因为我们要看的是时间分片的流程,所以会走到renderRootConcurrent中,
renderRootConcurrent会执行prepareFreshStack。这里有个知识点,fiber树的根节点是不变的,但是剩余部分,从app组件开始,渲染出的子树,是会有两颗,一颗树是现在DOM所对应的,渲染好的树,另一颗是reconcile过程构建的树,fiber树的根节点的current字段,会指向子树的根节点(也就是app组件对应的fiber节点)。根节点的current每次都会在更新完成后指向新的子树。
所以这里的workinProgress就是拿root.current来初始化的,对应了新的fiber子树构建开始,这时workInProgress就有值了。
但 renderRootConcurrent还没走完,会一直循环workLoopConcurrent,因为我们想看并发执行,也就是进入workLoopConcurrent函数。
这个函数的很简单:
注释也写了,就是在workInProgress不为空而且未Yield的情况下,一直循环执行performUnitOfWork。 这里的shouldYield是一个涉及了优先级和过期时间的综合判断,是否应该让渡出控制权。因为不是今天主题不过多介绍。我们暂且考虑它不让度控制权的情况。可以看到理想情况下,只要workInProgress存在时,就会一直执行performUnitOfWork。
performUnitOfWork做了什么呢?enableProfilerTimer主要是统计组件渲染时间,不看也罢,能看出来主流程就是执行beginwork,当返回值不为空,那么返回值就会成为新的workInProgress,所以大概可以猜测到,beginwork内部做了某些遍历工作,让每个workInProgress都得到处理。
可以看到beginWork的代码中有许多case,会根据workInProgress的tag,也就是fiber的tag,进入不同的处理分支,无论是哪个分支,其目的都是拿到fiber对应的children react element,比如函组件的children就是函数组件执行返回的jsx,dom元素的children就是对应的子元素。
然后reconcileChildren会为上面说的children react element创建fiber节点并返回,所以可以知道beginWork是返回了他的子fiber节点,子fiber节点又作为新的workInProgress,循环执行performUnitOfWork,一直到beginWork返回null,也就是没有子fiber节点,才会进入completeUnitOfWork。再这个过程中,会逐步构建出一颗和老的子树,也就是current对应的,新的fiber子树