前言
自从react16.8.0版本出来fiber概念以后,这个就成为面试的重点,如果面试官问你fiber的相关问题,你是否能答得出来呢
Fibers
为了组织工作单元,我们将需要一个数据结构:一个fiber树
我们将每一个元素对应一个fiber,每一个fiber就是一个工作单元
让我们展示给你一个例子。
假设我们想要渲染一个像这样的元素树:
js
Didact.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
container
)
在这个render
中,我们将创建一个root fiber,然后设置他作为nextUnitOfWork
. 剩余的工作将发生在performUnitOfWork
函数, 我们将为每一个fiber做以下三件事情:
- 添加元素到DOM中
- 为每一个元素的子元素创建fiber
- 选择下一个工作单元
这个数据结构的目标之一是为了可以方柏霓的找到下一个工作单元。 这也就是为什么每一个fiber都与他的第一个孩子2,他的兄弟节点和他的父母有链接。
当我们完成了在每一个fiber上的performing work,如果它有一个子节点那么fiber将作为下一个工作单元。
从我们的例子来看,当我们完成了在div 这个fiber上的工作,那么下一个工作单元就是h1 fiber.
如果这个fiber没有一个子节点,我们会使用兄弟节点作为下一个工作单元。
例如,这个p fiber没有一个子节点,所以我们在完成它以后转移到a fiber.
如果一个fiber既没有子节点,也没有兄弟节点,那么我们就去找'叔叔':这个父母的兄弟,例如像a和h2这样的fibers
如果这个父节点没有兄弟节点,我们会继续这个父节点向上找,直到我们找到一个有兄弟的父节点,或者直到根节点。如果我们已经到达了根节点,那么也就意味着我们已经完成所有渲染的工作。
现在让我们来用代码实现。
首先,让我们移除这些代码从render函数中。
js
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)
}
我们保留创建一个DOM节点这一块内容,我们稍后还会用到它。
在这个render函数中我们设置nextUnitOfWork
为这个fiber树的根节点。
js
function render(element, container) {
nextUnitOfWork = {
dom: container,
props: {
children: [element],
},
}
}
然后,当这个浏览器已经准备好的时候,它将调用我们的workLoop
函数,我们也将从这个根部开始工作。
js
function workLoop(deadline) {
nextUnitOfWork = performUnitOfWork(
nextUnitOfWork
)
}
function performUnitOfWork(fiber) {
// TODO 添加dom节点
// TODO 创建一个新的fiber
// TODO 返回下一个工作单元
}
首先,我们创建一个新节点,然后添加它到这个DOM中,我们保持这个DOM 节点的追踪,在这个fiber.dom属性中。
js
function performUnitOfWork(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
// TODO 创建一个新的fiber
// TODO 返回下一个工作单元
}
然后为每一个子节点都创建一个新的fiber。
js
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,
}
}
接下来我们添加它到我们的fiber树中,也设置它作为一个子节点或者作为一个兄弟节点,依赖于是否它是第一个子元素。
js
if (index === 0) {
fiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}
prevSibling = newFiber
index++
最终我们查找下一个工作节点。我们首先尝试他的子节点,然后是兄弟节点,然后是他的叔叔,等等。
js
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
这就是最终我们的performUnitOfWork
函数
js
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
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++
}
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
}