Build your own React_5 渲染和提交阶段

Posted 一只前端小马甲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Build your own React_5 渲染和提交阶段相关的知识,希望对你有一定的参考价值。

前端工程师的要求越来越高,仅懂得“三大马车”和调用框架API,已经远不能满足岗位的能力要求。因此增强自身的底层能力,了解框架的内部原理非常重要。本系列文章,翻译自Rodrigo Pombo的《Build your own React》一文,同时每篇文章最后,都会加入自己的理解,一方面记录自己初探React框架原理的过程,另一方面也是想与各位大牛多多交流,以出真知。

我们打算从零开始重写一个React框架,在遵循源码架构的基础上,省略了一些优化和非核心功能代码。

假设你阅读过我之前的文章《build your own React》,那篇文章是基于React 16.8版本,当时还不能使用hooks来替代class。

你可以在Didact仓库找到那篇文章和对应的代码。这里还有个相同主题的视频,跟文章的内容有些区别的,但是可以参考观看。

从零开始重写React框架,我们需要遵循以下步骤:

步骤五:渲染和提交阶段

我们现在有另一个问题:每次渲染一个元素,我们都会往DOM中添加一个新节点。还记得前面说过,在完成整个DOM树渲染之前,浏览器可以打断我们的工作。这么来看,用户会看到不完整的UI,显然我们不希望这样。

// requestIdleCallback(workLoop)

function performUnitOfWork(fiber){
	// if(!fiber.dom){
	// fiber.dom = createDom(fiber)
	// }

	if(fiber.parent){
		fiber.parent.dom.appendChild(fiber.dom)
	}

	// const elements = fiber.props.children
	// let index = 0
	// let prevSibling = null
}

我们删除performUnitOfWork中,修改DOM的代码。

// requestIdleCallback(workLoop)

function performUnitOfWork(fiber){
	// if(!fiber.dom){
	// fiber.dom = createDom(fiber)
	// }

	// const elements = fiber.props.children
	// let index = 0
	// let prevSibling = null
}

取而代之,我们一直追溯纤维树的根节点,并把它称之为正在运行的根节点或wipRoot。

// function render(element, container){
	wipRoot = {
	//	dom: container,
	//	props: {
	//		children: [element],
	//	}
	//}
	nextUnitOfWork = wipRoot
}

// let nextUnitOfWork = null
let wipRoot = null

一旦完成了所有工作(我们知道什么时候没有下一个渲染单元),我们把整个纤维树提交给DOM。

function commitRoot(){
	// TODO add nodes to dom
}

// function render(element, container){
//	wipRoot = {
//		dom: container,
//		props: {
//			children: [element]
//		}
//	}
//	nextUnitOfWork = wipRoot
// }

// let nextUnitOfWork = null
// let wipRoot = null

function workLoop(deadline){
//	let shouldYield = false
//	while(nextUnitOfWork && !shouldYield){
//		nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
//		shouldYield = deadline.timeRemaining() < 1
//	}

	if(!nextUnitOfWork && wipRoot){
		commitRoot();
	}
//	requestIdleCallback(workLoop)
}

// requestIdleCallback(workLoop)

提交纤维树的工作在commitRoot函数中完成,在这里我们会递归地将所有的节点添加至DOM中。

function commitRoot(){
	commitWork(wipRoot.child)
	wipRoot = null
}

function commitWork(fiber){
	if(!fiber){
		return
	}
	const domParent = fiber.parent.dom
	domParent.appenChild(fiber.dom)
	commitWork(fiber.child)
	commitWork(fiber.sibling)
}

总结

步骤四中,作者提出了纤维的概念,结合步骤三的并发模式,详细介绍了把任务拆成小单元的实现。

步骤五则指出了并发模式存在的问题,每个纤维渲染完成都可以被浏览器打断,用户会看到不完整的UI界面。

实际上作者在前面的章节总共考虑了两种极端情况:

  1. 囫囵吞枣”思路:递归渲染UI,主线程一直阻塞直到所有渲染任务完成
  2. 细嚼慢咽”思路:渲染任务被拆成了小单元,任意单元渲染完都能被浏览器中止渲染过程

显然,这两者都不好,程序设计上来说也应当符合“中庸”之道,作者此时提出了改进思路——只有当整个纤维树完成创建节点时,才会整体添加至DOM中。这实际上就是将两种思路结合的结果。

上一篇传送门:Build your own React_4 理解React纤维
下一篇传送门:Build your own React_6 调解器

以上是关于Build your own React_5 渲染和提交阶段的主要内容,如果未能解决你的问题,请参考以下文章

Build your own React_4 理解React纤维

Build your own React_4 理解React纤维

Build your own React_0 总述

Build your own React_0 总述

Build your own React_8 Hooks

Build your own React_7 函数组件