Build your own React_4 理解React纤维
Posted 一只前端小马甲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Build your own React_4 理解React纤维相关的知识,希望对你有一定的参考价值。
前端工程师的要求越来越高,仅懂得“三大马车”和调用框架API,已经远不能满足岗位的能力要求。因此增强自身的底层能力,了解框架的内部原理非常重要。本系列文章,翻译自Rodrigo Pombo的《Build your own React》一文,同时每篇文章最后,都会加入自己的理解,一方面记录自己初探React框架原理的过程,另一方面也是想与各位大牛多多交流,以出真知。
我们打算从零开始重写一个React框架,在遵循源码架构的基础上,省略了一些优化和非核心功能代码。
假设你阅读过我之前的文章《build your own React》,那篇文章是基于React 16.8版本,当时还不能使用hooks来替代class。
你可以在Didact仓库找到那篇文章和对应的代码。这里还有个相同主题的视频,跟文章的内容有些区别的,但是可以参考观看。
从零开始重写React框架,我们需要遵循以下步骤:
步骤四:理解React纤维
为了组织渲染任务单元,我们需要一种数据结构:纤维树。
每个React元素都跟唯一纤维对应,纤维也表示一个渲染任务单元。
这里有个例子,假如你想渲染的元素树如下:
Didact.render(
<div>
<h1>
<p />
<a />
</h1>
<h2></h2>
</div>,
container
)
在render函数中我们创建根纤维并将其设置为下个渲染任务单元(nextUnitOfWork)。剩下的工作都会在performUnitOfWork函数中进行,对于任意纤维,我们需要做三件事情:
- 将元素添加至DOM
- 创建元素的子元素的纤维
- 选择下个渲染任务单元
使用这种数据结构的目的在于:轻松地找到下个渲染任务单元。这也是为什么每个纤维跟它相邻的纤维都有连接,这个相邻的纤维可能是第一个子纤维,第一个子纤维的兄弟纤维或者是父纤维。
当完成了一个纤维的渲染工作,它的子纤维会成为下个渲染任务单元。
在我们的例子中,当我们完成了div纤维的渲染工作,接下来要对h1进行渲染。
如果纤维没有子纤维,我们把它的兄弟纤维作为下个渲染任务单元。
例如下图,p元素对应纤维没有子纤维,因此当p渲染完成之后我们会渲染a纤维。
如果纤维既没有子纤维,又没有兄弟纤维,我们则考虑它的“叔叔”纤维——父纤维的兄弟纤维。例如上图中的a和h2。
同时,如果父纤维没有兄弟纤维,我们就继续往上找,直至找到“叔叔”纤维或者根纤维。如果到达了根纤维,表示整个渲染工作已经完成。
接下来,我们来看看代码。
首先,我们将步骤二中的render函数某些代码删除。
function render(element, container)
const dom =
element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type)
const isProperty = key => key !== "children"
Object.keys(element.props)
.filter(isProperty)
.forEach(name =>
dom[name] = element.props[name]
)
element.props.children.forEach(child=>
render(child, dom)
)
container.appendChild(dom)
let nextUnitOfWork = null
我们先将创造DOM节点的部分保存在createDom函数中,我们之后再用它。
function createDom(fiber)
// const dom =
// fiber.type === "TEXT_ELEMENT"
// ? document.createTextNode("")
// : document.createElement(fiber.type)
// const isProperty = key = key !== "children"
// Object.keys(fiber.props)
// .filter(isProperty)
// .forEach(name =>
// dom[name] = fiber.props[name]
// )
// return dom
function render(element, container)
// TODO set next unit of work
let nextUnitOfWork = null
在render函数中,我们将纤维树的根纤维设置为下个渲染任务单元。
function render(element, container)
nextUnitOfWork =
dom: container,
props:
children: [element],
,
然后,当浏览器准备好后,它会调用我们的workLoop函数,我们将从根元素开始渲染。
function workLoop(deadline)
// let shouldYield = false
// while(nextUnitOfWork && !shouldYield)
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
// shouldYield = deadline.timeRemaining() < 1
//
// requestIdleCallback(workLoop)
// requestIdleCallback(workLoop)
function performUnitOfWork(fiber)
// TODO add dom node
// TODO create new fibers
// TODO return next unit of work
首先,我们创建一个新的节点,并将它添加至DOM中。
我们通过fiber的dom属性来获取相应的DOM节点。
// function performUnitOfWork(fiber)
if(!fiber.dom)
fiber.dom = createDom(fiber)
if(fiber.parent)
fiber.parent.dom.appendChild(fiber.dom)
// TODO create new fibers
// TODO return next unit of work
//
然后,对纤维的每个子元素创造相应的纤维。
// if(fiber.parent)
// fiber.parent.dom.appendChild(fiber.dom)
//
const elements = fiber.props.children
let index = 0
let prevSibling = null
while(index < elements.length)
const element = elements[index]
const newFiber =
type: element.type,
props: element.props,
parent: fiber,
dom: null,
// TODO return next unit of work
接着,我们将新生成的纤维加入纤维树,它可能是个子纤维或者是兄弟纤维,取决于他是否是父纤维的第一个子纤维。
// const element elements[index]
// const newFiber =
// type: element.type,
// props: element.props,
// parent: fiber,
// dom: null
//
if(index===0)
fiber.child = newFiber
else
prevSibling.sibling = newFiber
prevSibling = newFiber
index++
// TODO return next unit of work
最后,我们来找到下个渲染任务单元。我们首先尝试下子纤维,其次是兄弟纤维,然后是”叔叔“纤维,以此类推。
// prevSibling = newFiber
// index++
//
if(fiber.child)
return fiber.child
let nextFiber = fiber
while(nextFiber)
if(nextFiber.sibling)
return nextFiber.sibling
nextFiber = nextFiber.parent
这就是我们的performUnitOfWork函数。
function performUnitOfWork(fiber)
// create a new dom node
if(!fiber.dom)
fiber.dom = createDom(fiber)
if(fiber.parent)
fiber.parent.dom.appendChild(fiber.dom)
// create new fibers
const elements = fiber.props.children
let index = 0
let prevSibliing = null
while(index < elements.length)
const element = elements[index]
const newFiber =
type: element.type,
props: element.props,
parent: fiber,
dom: null
if(index===0)
fiber.child = newFiber
else
prevSibling.sibling = newFiber
prevSibling = newFiber
index++
// return next unit of work
if(fiber.child)
return fiber.child
let nextFiber = fiber
while(nextFiber)
if(nextFiber.sibling)
return nextFiber.sibling
nextFiber = nextFiber.parent
总结
步骤三中,作者提出了通过把整个渲染任务拆成小单元,来解决传统渲染无法中止问题的思路。
步骤四则详细介绍了把任务拆成小单元的实现,并给小单元了个新的名字——纤维。纤维是一种自定义的数据结构,反映了原始React元素的信息,同时也能获取到最终渲染的DOM节点,React并不是将元素树直接渲染为DOM,而是将元素树转化为fiber树的同时渲染DOM。我们在步骤三并发模式中知道了performUnitOfWork会获取下个渲染任务单元,实际上就是获取下个fiber,因此performUnitOfWork的实现非常重要。获取下个fiber需要三步:首先根据当前对象生成DOM,并添加至父节点;其次将当前对象的所有子元素遍历,生成相应的fiber;最后返回下个fiber。
上一篇传送门:Build your own React_3 并发模式
下一篇传送门:Build your own React_5 渲染和提交阶段
以上是关于Build your own React_4 理解React纤维的主要内容,如果未能解决你的问题,请参考以下文章