Fibers

为了组织工作单位,我们将需要一个数据结构:fiber three。

我们让每个元素对应一个 fiber,每个 fiber 都是一个工作单位。

举个例子:

加入我们需要渲染的元素树如下:

Didact.render(
<div>
<h1>
<p />
<a />
</h1>
<h2 />
</div>,
container
)

在渲染时,我们将创建 root fiber,并将其设置为下一个工作单元 nextUnitOfWork,其他的处理则在 performUnitOfWork 函数中执行,该函数主要做三件事:

  • 把元素添加到 dom 中
  • 为元素的子元素都创建一个 fiber 结构
  • 找到下一个工作单元

采用 fiber 数据结构的原因在于更方便的找到下一个工作单元。如图,每个 fiber 都会链接到其 子节点、兄弟节点和父节点。

流程就不翻了,大致意思就是 父找子,无子则找弟,无子无弟则找父

接下来,修改 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)
}

第一步:抽出 createDom 用于创建 dom 节点

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
}

第二步:在 render 函数中设置 nextUnitOfWork,作为 fiber tree 的根节点

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

至此,当浏览器空闲时,则会执行上一节实现的 workLoop 函数,即开始执行 fiber tree 的根节点工作单元。

第三步:实现 performUnitOfWork 函数

function performUnitOfWork(fiber) {
// TODO add dom node
// TODO create new fibers
// TODO return next unit of work
}
function performUnitOfWork(fiber) {
/**
* 1. add dom node
* 创建 dom 元素,将其挂载到 fiber 的 dom 属性上,并将创建的 dom 元素添加至父元素上
*/
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
if (fiber.parent) {
fiber.parent.dom.appendChild(fiber.dom)
}
/**
* 2. create new fibers
* 为每一个子元素创建相应的 fiber
* 并将其设置为前一个 fiber 节点的子元素或兄弟元素
*/
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++
}
/**
* 3. 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
}
}