「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
实现
我们将分为8步,一步一步的实现一个小型的React
- 实现createElement函数
- 实现render函数
- Currnet Mode模式
- fibers
- render和commit阶段
- 协调器
- function组件
- hooks
function组件
react16支持函数组件,下面我们来实现一下,来支持function组件。
先来看一个简单的函数组件:
function App(props) {
return <h1>Hi {props.name}</h1>
}
const element = <App name="foo" />
const container = document.getElementById("root")
Didact.render(element, container)
// 将JSX转换为js为:
function App(props) {
return Didact.createElement(
"h1",
null,
"Hi ",
props.name
)
}
const element = Didact.createElement(App, {
name: "foo",
})
element 等价于
{
type: App,
props: { children: [], name: "foo" }
}
函数组件不同之处为children节点来自函数组件执行后返回的结果
我们可以根据fiber的类型来决定要使用哪一种获取dom的方法,如果是函数组件就进入updateFunctionComponent方法,如果host组件就进入updateHostComponent方法,updateHostComponent方法与之前的实现一致。
function performUnitOfWork(fiber) {
const isFunctionComponent = fiber.props.children[0].type instanceof Function;
if (isFunctionComponent) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
省略下面代码。。。
}
function updateFunctionComponent(fiber) {
// TODO
}
function updateHostComponent(fiber) {
if (!fiber.dom) {
fiber.dom = createDom(fiber)
}
reconcileChildren(fiber, fiber.props.children)
}
在函数组件里,我们需要执行functino以获取children,举个栗子,children.type就是App函数组件,执行它就能获取到它返回的h1节点,我们获取到children节点,后面reconcileChildren的流程是一样的,不需要修改。
function updateFunctionComponent(fiber) {
const children = [fiber.props.children[0].type(fiber.props)]
reconcileChildren(fiber, children)
}
接下来要修改commitWork方法,在commitWork方法里的fiber没有dom节点,先看下之前的代码:
function commitWork(fiber) {
if (!fiber) {
return
}
let domParent = fiber.parent.dom;
if (fiber.effectTag === 'PALCEMENT' &&
fiber.dom != null
) {
// 如果标记是PALCEMENT并且dom存在,直接复用dom
domParent.appendChild(fiber.dom);
} else if (fiber.effectTag === 'UPDATE' &&
fiber.dom != null
) {
// 如果标记是UPDATE并且dom不存在,更新dom
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectTag === 'DELETION') {
// 如果标记是DELETION,我们直接删除dom
domParent.removeChild(fiber.dom);
}
commitWork(fiber.child);
commitWork(fiber.sibling);
}
commitWork方法要做出两处修改:
- 先找到存在dom节点的parent,如果当前fiber.parent不存在dom,就继续向上查找fiber.parent.parent,只到找到为止
- 在删除节点时,我们还需要继续往前查找,直到找到一个具有DOM节点的child
function commitWork(fiber) {
if (!fiber) {
return
}
let domParentFiber = fiber.parent;
// 向上查找存在dom节点的parent
while (!domParentFiber.dom) {
domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.dom;
if (fiber.effectTag === 'PALCEMENT' &&
fiber.dom != null
) {
// 如果标记是PALCEMENT并且dom存在,直接复用dom
domParent.appendChild(fiber.dom);
} else if (fiber.effectTag === 'UPDATE' &&
fiber.dom != null
) {
// 如果标记是UPDATE并且dom不存在,更新dom
updateDom(fiber.dom, fiber.alternate.props, fiber.props);
} else if (fiber.effectTag === 'DELETION') {
// 如果标记是DELETION,我们直接删除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);
}
}
总结
function组件与host组件最大的区别是fucntion组件需要执行才能获取到children