渐进式剖析React源码(2):不得不谈的workLoop

184 阅读4分钟

image.png

workLoop是react中一个非常重要的执行函数,今天的这篇文章我们需要完成下面几个小目标:

  1. 理解什么是workLoop?
  2. 熟悉workLoop的流程是什么?
  3. 掌握workLoop源码是如何实现的?

在开始今天的文章前,我们先从上文中关键的一句话入手。

react把原先一个完整的diff任务拆成颗粒度更细的任务

这里提到的颗粒度更细的任务,任务其实指的是构建fiber数据结构这样的事情,所以上文可以翻译成react把原先一个完整的diff工作拆成了很多构建fiber数据结构的任务。那我们需要看看一个fiber,他的数据结构到底长什么样子?

源码位置------fiber数据结构

class FiberNode {
	return: FiberNode | null;
	child: FiberNode | null;
	sibling: FiberNode | null;
	index: number;

	pendingProps: Props;
	memorizedProps: Props;
	memorizedState: Props;
	updateQueue: any;

	key: Key;
	tag: workTags;
	type: any;
	stateNode: any;
	ref: Ref;

	alternate: FiberNode | null;
	flags: Flags;
	subTreeFlags: Flags;
	deletions: FiberNode[] | null;
	constructor(tag: workTags, pendingProps: Props, key: Key) {
		// 描述fiber节点关系
		this.return = null;
		this.child = null;
		this.sibling = null;
		this.index = 0;
		// 描述状态
		this.pendingProps = pendingProps;
		this.memorizedProps = null;
		this.memorizedState = null;
		this.updateQueue = null;
		// 描述dom
		this.tag = tag;
		this.type = null;
		this.stateNode = null;
		this.key = key || null;
		this.ref = null;

		this.alternate = null;
		this.flags = Noflags;
		this.subTreeFlags = Noflags;
		this.deletions = null;
	}
}

所以他其实就是个对象,里面包含了各个fiber之间的关系,状态、dom信息、副作用等等。那么多个fiber就是通过上面retun、child、sibling连接起来的,构成了一颗fiber树。

image.png

如上图,一个个fiber相互联系了起来,它和以往熟知的virtualDom树不同,它是个链表结构,为什么要做成这样的数据结构?它是通过什么构建成这样的结构?这些我们留到后续的文章中详细讲解,目前只需要明白,一个个fiber的构建是react内部的任务。

ok 我们转到今天的主角------workLoop

老规矩 先说结论: 理念上它类比于事件循环,如果队列中存在任务,则一直从中取出任务去执行,而这里的任务想必大家已经知道是什么了吧,它就是上面提到的一个个fiber单元的构建。so, workLoop干的就是这个事情。

这就是第一个小目标的答案,在react源码中我也标注出来 workLoop

while (workInProgress !== null) {
  performUnitOfWork(workInProgress);
}

如上,它开启的是个while循环,不断的去判断当前的workInProgress是否存在,若存在则继续执行构建的任务。

那么具体performUnitOfWork做了什么事情源码位置

源码简化来看其实就是如下:

const performUnitOfWork = (fiber: FiberNode) => {
    // 先深度优先遍历 模拟递的过程
    const next = beginWork(fiber, wipRootRenderLane);
    fiber.memorizedProps = fiber.pendingProps;
    if (next === null) {
      // 没有子fiber
      do {
           completeWork(fiber);
           const sibling = fiber.sibling;
           if (sibling) {
              return (workInProgress = sibling);
           }
           fiber = fiber.return;
           workInProgress = fiber;
	} while (fiber !== null);
    } else {
      workInProgress = next;
    }
};

我们在这篇文章中,不会深究具体某个函数内部做了什么事情,我们关注点放在workLoop是如何把任务进行串联起来的。所以抽象看起来,发现内部其实执行了2个函数,一个是beginWork、completeWork,他们会去修改workInProgress的值,由于workInProgress被修改了从而导致不断的驱动workLoop进行while循环

workInProgress其实是个全局的变量,标记当前运行到哪个fiber单元了。

整个构建调度的过程,其实和递归逻辑一样,都是深度优先遍历。 beginWork和completeWork会交替执行,下面是他们运行过程的文字表述:

  1. 先遍历到叶子节点(beginWork做的事,向下走),next === null代表目前是叶子节点,没有子节点next了,这一阶段的beginWork结束。
  2. 进入到completeWork函数,完成一系列操作(后续详解),它执行过程会判断是否有兄弟节点存在(fiber.sibling === true),如果存在那么直接打断,修改兄弟节点为workInProgress,这样兄弟节点会进行beginWork阶段,走第一步的操作。
  3. 若兄弟节点不存在,就会走向父级节点(fiber=fiber.return;workInProgress = fiber),再走到判断兄弟节点是否存在的逻辑,一直向上追溯到顶级节点,直到null,构建结束。

相信光看文字表述还是不太好理解,所以配置了流程图,大家可以参照流程图与上述的表达对应起来,就能清晰的明白workLoop是如何调度fiber工作的构建的。

image.png

ok,以上就是文章的所有内容了,我们总结下:

  1. react中的任务被拆分成一个个fiber单元构建。
  2. fiber数据结构是链表,记录了各个节点关系,dom信息、状态等等。
  3. workLoop就是维护第一点不断构建的调度逻辑。
  4. workLoop本身是用遍历模拟递归的方式实现,内部主要是2个函数,beginWork(类似递归中的递)、completeWork(类似递归中的归),整个过程是交替执行这2个函数,直到整棵fiber结构构建完成。

同样,了解了这些内容,相信会带来更多的疑问,譬如:

  1. 开发者写的代码是如何唤起workLoop执行的?
  2. 整棵fiber结构又是如何转换成页面?
  3. beginWork内部具体做了什么?
  4. complete内部又做了什么?
  5. 。。。。

so,让我们带着问题去期待下一篇文章~