手写react五

102 阅读2分钟

「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。

实现

我们将分为8步,一步一步的实现一个小型的React

  • 实现createElement函数
  • 实现render函数
  • Currnet Mode模式
  • fibers
  • render和commit阶段
  • 协调器
  • function组件
  • hooks

render和commit阶段

React中,render阶段主要的工作是完成fiber tree的构建,commit阶段主要是执行dom操作,接下来我们继续改造代码。

function performUnitOfWork(fiber) {
    // 创建dom
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    if (fiber.parent) { // 每次添加新节点
        fiber.parent.dom.appendChild(fiber.dom);
    }
    // 为每个child创建fiber节点 省略。。。
    // 返回下一个任务 省略。。。
}

我们来看一下上次实现的代码,这里存在一个问题,我们在每个小任务中都会向dom添加新节点。

还记得前面说过,当浏览器有空闲时间才执行任务,也就是说浏览器会打断我们的任务,如果还没有完成构建整个fiber tree,用户可能会看到不完整的UI,这并不是我们想要的效果。

所以我们要移除这段代码,怎么解决这个问题呢?我们可以用一个全局变量来记录这个未完成的fiber tree,因为所有这些任务都是在内存中实现的,将这个变量命名为workInProcessFiber

function performUnitOfWork(fiber) {
    // 创建dom
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    // 为每个child创建fiber节点 省略。。。
    // 返回下一个任务 省略。。。
}
function render(element, container) {
    workInProcessFiber = {
        dom: continer,
        props: {
            children: [element]
        },
    }
    nextUnitOfWork = workInProcessFiber
}
let nextUnitOfWork = null;
let workInProcessFiber = null;

当完成所有任务时,也就是完成构建fiber tree时,render阶段已经完成,我们提交fiber tree进入commit阶段。

function performUnitOfWork(fiber) {
    // 创建dom
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    // 为每个child创建fiber节点 省略。。。
    // 返回下一个任务 省略。。。
}
function commitRoot() {
    // TODO 执行dom操作
}
function render(element, container) {
    workInProcessFiber = {
        dom: continer,
        props: {
            children: [element]
        },
    }
    nextUnitOfWork = workInProcessFiber
}
let nextUnitOfWork = null;
let workInProcessFiber = null;

function workLoop(deadline) {
    let shouldYield = false
    while (nextUnitOfWork && !shouldYield) { // 存在下一个任务并且存在空闲时间
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
        shouldYield = deadline.timeRemaining() < 1
    }
    if (!nextUnitOfWork && wipRoot) { // 构建完成,提交root fiber
        commitRoot();
    }
    requestIdleCallback(workLoop)
}

下面实现commitRoot方法,我们递归地将子节点加入到dom节点中,在这个方法里一次性执行完daom操作,就解决了可能会被浏览器打断从而会看到不完整UI的情况。

function performUnitOfWork(fiber) {
    // 创建dom
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    // 为每个child创建fiber节点 省略。。。
    // 返回下一个任务 省略。。。
}
function commitRoot() {
    commitWork(workInProcessFiber.child);
    workInProcessFiber = null;
}
function commitWork(fiber) {
    // 执行dom操作
    if (!fiber) {
        return;
    }
    const domParent = fiber.parent.dom;
    domParent.appendChild(fiber.dom);
    commitWork(fiber.child);
    commitWork(fiber.sibling);
}
function render(element, container) {
    workInProcessFiber = {
        dom: continer,
        props: {
            children: [element]
        },
    }
    nextUnitOfWork = workInProcessFiber
}

总结

  1. 浏览器可能会打断fiber tree的构建,所以记录在内存中构建的workInProcessFiber
  2. render阶段的主要任务是从root开始,为每个child创建fiber节点,直到完成fiber tree的构建
  3. 当构建完成,就会提交root fiber,进入commit阶段