手写react三

128 阅读1分钟

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

实现

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

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

Currnet Mode模式

接下来我们之前的代码进行重构,上次也说到了,问题是递归地渲染dom节点,一旦开始渲染,就必须得渲染完整个dom树,如果dom树巨大,可能会占用主线程太多时间。

此时如果浏览器需要执行优先级比较高的任务比如用户输入,必须等到渲染完成,对用户来说,实在不够友好。

所以我们需要将整个渲染过程分成一个一个小任务,当我们完成一个小任务后,如果浏览器需要完成其他内容,我们将让浏览器中断渲染。

let nextUnitOfWork = null // 记录下一个任务

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) { // 存在下一个任务并且存在空闲时间
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork) {
  // TODO
}

这里我们使用requestIdleCallback来循环执行任务,而React使用的时自己实现的调度器,但原理都是一样的,在浏览器有空闲时间的时候执行我们的任务

requestIdleCallback方法会执行我们传入的回调函数并传入一个deadline参数,我们可以使用deadline来判断是否还有剩余的空闲时间

完整代码:

const Didect = {
    createElement,
    render,
}
let nextUnitOfWork = null // 记录下一个任务

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) { // 存在下一个任务并且存在空闲时间
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(nextUnitOfWork) {
  // TODO
}
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]
        })
    container.appendChild(dom)
}
const element = (
    <div id="foo">
        <a>bar</a>
        <b />
    </div>
)
const container = document.getElementById("root") 
Didect.render(element, container)

总结

  1. 为了实现可中断渲染,我们完整的渲染分成一个一个小的渲染任务
  2. 将递归渲染改为循环渲染
  3. 使用requestIdleCallback方法,当浏览器有空闲时间时才继续执行下一个任务

要开始循环执行任务,我们需要设置首个任务和实现performUnitOfWork方法,performUnitOfWork将返回下一个需要执行的任务。