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界面。
实际上作者在前面的章节总共考虑了两种极端情况:
- “囫囵吞枣”思路:递归渲染UI,主线程一直阻塞直到所有渲染任务完成
- “细嚼慢咽”思路:渲染任务被拆成了小单元,任意单元渲染完都能被浏览器中止渲染过程
显然,这两者都不好,程序设计上来说也应当符合“中庸”之道,作者此时提出了改进思路——只有当整个纤维树完成创建节点时,才会整体添加至DOM中。这实际上就是将两种思路结合的结果。
上一篇传送门:Build your own React_4 理解React纤维
下一篇传送门:Build your own React_6 调解器
以上是关于Build your own React_5 渲染和提交阶段的主要内容,如果未能解决你的问题,请参考以下文章
Build your own React_4 理解React纤维