手写react七

167 阅读2分钟

「这是我参与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方法要做出两处修改:

  1. 先找到存在dom节点的parent,如果当前fiber.parent不存在dom,就继续向上查找fiber.parent.parent,只到找到为止
  2. 在删除节点时,我们还需要继续往前查找,直到找到一个具有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