Function Components

本节内容实现对函数式组件的支持,如下,一个简单的例子:

/** @jsx Didact.createElement */
function App(props) {
return <h1>Hi {props.name}</h1>
}
const element = <App name="foo" />
const container = document.getElementById("root")
Didact.render(element, container)

函数式组件有两个不同的地方:

  • 函数式组件对应的 fiber 没有 dom 节点
  • 函数式组件的子元素并不在其 props 上,而是通过 return 返回

所以,在 performUnitOfWork 操作工作单元时,需要根据是否是函数式组件做出不同的更新操作:

function performUnitOfWork(fiber) {
const isFunctionComponent = fiber.type instanceof Function
if (isFunctionComponent) {
updateFunctionComponent(fiber)
} else {
updateHostComponent(fiber)
}
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
}
function updateFunctionComponent(fiber) {
// TODO
}
function updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
reconcileChildren(fiber, fiber.props.children)
}

updateFunctionComponent 主要也是创建 dom 及协调子元素。在 updateFunctionComponent 中,我们需执行函数式组件获得子元素。

在本节的例子中,fiber.type 是函数 APP,当运行它时,返回 h1 元素。

此外,一旦返回的有子元素,其协调方式是一样的,即不需要更改任何内容,所以 updateFunctionComponent 如下:

function updateFunctionComponent(fiber) {
const children = [fiber.type(fiber.props)]
reconcileChildren(fiber, children)
}

接下来,需要修改函数 commitWork,对于没有 DOM 节点的 fibers 节点,我们需要做以下两件事情:

首先,需要在 fiber tree 上往上查找,知道找到一个拥有 DOM 的 fiber 节点。

另外,当我们需要删除一个节点时,也需要继续查找直至找到一个有 DOM 节点的子节点。

故,commitWork 做出以下修改:

function commitWork(fiber) {
if (!fiber) {
return
}
+ let domParentFiber = fiber.parent
+ while (!domParentFiber.dom) {
+ domParentFiber = domParentFiber.parent
+ }
- const domParent = fiber.parent.dom
+ const domParent = domParentFiber.dom
if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
domParent.appendChild(fiber.dom)
} else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
updateDom(
fiber.dom,
fiber.alternate.props,
fiber.props
)
} else if (fiber.effectTag === "DELETION") {
- domParent.removeChild(fiber.dom)
+ commitDeletion(fiber, domParent)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
+function commitDeletion(fiber, domParent) {
+ if (fiber.dom) {
+ domParent.removeChild(fiber.dom)
+ } else {
+ commitDeletion(fiber.child, domParent)
+ }
+}