1、Fiebr 作为数据结构
每个Fiber节点有个对应的React element,多个Fiber节点是如何连接形成树呢?
例如我们有这样一个属性结构:
react.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
root
)
React Fiber 机制的实现,是依赖下面这种数据结构(链表),每一个节点都是一个fiber。一个 fiber 包括了 child(第一个子节点)、sibling(兄弟节点)、parent(父节点)属性。
// 指向父级Fiber节点
this.return = null;
// 指向子Fiber节点
this.child = null;
// 指向右边第一个兄弟Fiber节点
this.sibling = null;
2、构建过程
在构建中,我们将创建根fiber并将其设置为 nextUnitOfWork。剩下的工作将在 perforformunitofwork 功能上进行,每个fiber做了三件事:
- 将元素添加到 DOM
- 为元素的子元素创建fiber
- 选择下一个工作单元
实现目的是为了查找下一个工作单元变得更容易,使用的深度优先遍历,先查找子节点,在查找兄弟节点。
上图渲染过程详细描述如下:
- 当完成root的fiber工作时,如果有孩子,那么fiber是下一个工作的单元,root的子节点是div
- 当完成div的fiber工作时,下一个工作单元是h1
- h1的节点是p,继续下一个工作单元p
- p没有子节点,去找兄弟节点a
- a兄弟节点和子节点都没有,返回到父亲节点h1
- h1的子节点都已经工作完成了,去找h1的兄弟节点h2
- h2既没有兄弟节点,也没有子节点,返回到父亲节点div
- 同上,div在返回到父亲节点root
- 至此已经完成了所有的渲染工作
3、代码实现
1.抽离DOM节点的代码,放入到createDom()函数中;
/**
* 创建DOM
* @param {*} fiber fiber节点
*/
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
}
2.在 render 函数中,设置nextUnitOfWork为fiber的根节点,根节点只包含一个children属性;
export function render(element, container) {
// 将根节点设置为第一个将要工作单元
nextUnitOfWork = {
dom: container,
props: {
children: [element],
},
}
}
3.当浏览器存在空闲时间,开始处理根节点;
/**
* 处理工作单元,返回下一个单元事件
* @param {*} nextUnitOfWork
*/
function performUnitOfWork(fiber) {
// 如果fiber上没有dom节点,为其创建一个
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
// 如果fiber有父节点,将fiber.dom添加到父节点
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
}
3.为每一个孩子节点创建一个新的fiber;
function performUnitOfWork(fiber) {
// 省略上面内容
// 获取到当前fiber的孩子节点
const elements = fiber.props.children
// 索引
let index = 0
// 上一个兄弟节点
let prevSibling = null
// 遍历孩子节点
while (index < elements.length) {
const element = elements[index]
// 创建fiber
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
dom: null,
}
}
}
4.将新节点添加到fiber树中;
function performUnitOfWork(fiber) {
// 省略上面内容
while(index < elements.length){
// 省略上面内容
// 将第一个孩子节点设置为 fiber 的子节点
if (index === 0) {
fiber.child = newFiber
} else if(element) {
// 第一个之外的子节点设置为第一个子节点的兄弟节点
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
}
}
5.寻找下一个工作单元,先查找孩子,然后兄弟,如果没有就返回父节点;
function performUnitOfWork(fiber) {
// 省略上面内容
// 寻找下一个孩子节点,如果有返回
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
// 如果有兄弟节点,返回兄弟节点
if (nextFiber.sibling) {
return nextFiber.sibling
}
// 否则返回父节点
nextFiber = nextFiber.parent
}
}
4、效果
修改src/index.js文件
实现效果: