构建你的React【下】

221 阅读12分钟

构建你的React【下】

  • 步骤五:Render & Commit
  • 步骤六:Reconciliation
  • 步骤七:Function Components
  • 步骤八:Hooks

步骤 5:Render 和 Commit 阶段

我们这里有另一个问题。

function performUnitOfWork(fiber) {
  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
}

每次处理元素时,我们都会向 DOM 添加新节点。而且,请记住,浏览器可能会在我们完成渲染整个树之前中断我们的工作。在这种情况下,用户将看到不完整的 UI。我们不希望那样。

所以我们需要从这里删除改变 DOM 的部分。

// 删除
if (fiber.parent) {
  fiber.parent.dom.appendChild(fiber.dom)
}

相反,我们将跟 fiber 树的根。我们将其称为 work in progress root 或 wipRoot。

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element]
    }
  }
  nextUnitOfWork = wipRoot
}

let wipRoot = null

一旦我们完成了所有工作(我们知道这一点,因为没有下一个工作单元),我们将整个 fiber 树提交到 DOM。

function commitRoot() {
  // TODO add nodes to dom
}

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
​
   // 新增
  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }
​
  requestIdleCallback(workLoop)
}

我们在 commitRoot 函数中执行此操作。在这里,我们递归地将所有节点附加到 dom 中。

function commitRoot() {
  commitWork(wipRoot.child)
  wipRoot = null
}
​
function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  domParent.appendChild(fiber.dom)
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

步骤 5 完整代码

debugger
/**
 * performUnitOfWork在有fiber.parent的时候,直接appendChild不行,如果被浏览器暂停任务就不会显示完整页面
 * 所以要分render和commit阶段,在commit阶段递归
 */
const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        return typeof child === 'object' ? child : createTextElement(child)
      })
    }
  }
}

const createTextElement = (text) => {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue: text,
      children: []
    }
  }
}

// 根据fiber创建真实dom
const createDom = (fiber) => {
  // 创建对应节点
  const dom =
    fiber.type === 'TEXT_ELEMENT'
      ? document.createTextNode('')
      : document.createElement(fiber.type)

  // 过滤特殊的children
  const isProperty = (key) => {
    return key !== 'children'
  }

  // 赋给props
  Object.keys(fiber.props)
    .filter(isProperty)
    .forEach((name) => {
      dom[name] = fiber.props[name]
    })

  return dom
}

const render = (element, container) => {
  wipRoot = {
    dom: container,
    props: {
      children: [element]
    }
  }
  nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let wipRoot = null

function commitRoot() {
  // TODO add nodes to dom
  commitWork(wipRoot.child)
  wipRoot = null
}

function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  domParent.appendChild(fiber.dom)
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
    // 如果剩余时间少于 1 毫秒,则 shouldYield 被设置为 true,表示当前任务应该让出执行权。
    shouldYield = deadline.timeRemaining() < 1
  }

  // 为什么这里就不会被中断
  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }

  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

// fiber对象
// {
//   type
//   props
//   dom
//   parent
//   child
//   sibling
// }

// 传入fiber,创建dom,为children创建fiber,找到下一个工作单元
function performUnitOfWork(fiber) {
  // 1、创建DOM
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }

  // if (fiber.parent) {
  //   fiber.parent.dom.appendChild(fiber.dom)
  // }

  // 2、给children创建fiber
  const elements = fiber.props.children
  let index = 0
  let prevSibling = null

  while (index < elements.length) {
    const element = elements[index]

    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null
    }

    if (index === 0) {
      fiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }

    prevSibling = newFiber
    index++
  }

  // 3、找到下一个工作单元

  // 向下递,向上归
  if (fiber.child) {
    return fiber.child
  }

  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

const MyReact = {
  createElement,
  render
}

/** @jsx MyReact.createElement */
// const element = <h1 title="foo">Hello</h1>

const element = (
  <div style="background: salmon">
    <h1>Hello World</h1>
    <h2 style="text-align:right">from MyReact</h2>
  </div>
)

const container = document.getElementById('root')
MyReact.render(element, container)

步骤 6:Reconciliation

到目前为止,我们只向 DOM 添加了一些内容,但是更新或删除节点呢?

这就是我们现在要做的,我们需要将我们在 render 函数上接收到的元素与我们提交到 DOM 的最后一个 fiber 树进行比较。

因此,我们需要在完成提交后保存对“我们提交到 DOM 的最后一个 fiber 树”的引用。我们将其称为 currentRoot。

我们还为每个 fiber 添加了 alternate 属性。此属性是指向旧 fiber 的链接,即我们在上一个提交阶段提交到 DOM 的 fiber。

function commitRoot() {
  commitWork(wipRoot.child)
  // 新增
  currentRoot = wipRoot
  wipRoot = null
}


function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  }
  nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let currentRoot = null
let wipRoot = null

现在,让我们从 performUnitOfWork 中提取创建新 fiber 的代码...

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }
​
  const elements = fiber.props.children
  let index = 0
  let prevSibling = nullwhile (index < elements.length) {
    const element = elements[index]
​
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    }
​
    if (index === 0) {
      fiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }
​
    prevSibling = newFiber
    index++
  }
​
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

…添加到新的 reconcileChildren 函数。

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }
​
  const elements = fiber.props.children
  reconcileChildren(fiber, elements)
​
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

function reconcileChildren(wipFiber, elements) {
  // TODO:
}

​ 在这里,我们将调和旧 fiber 与新元素。

function reconcileChildren(wipFiber, elements) {
  let index = 0
  let prevSibling = nullwhile (index < elements.length) {
    const element = elements[index]
​
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: wipFiber,
      dom: null,
    }
​
    if (index === 0) {
      wipFiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }
​
    prevSibling = newFiber
    index++
  }
}

我们同时迭代旧 fiber (wipFiber.alternate) 的子元素和我们想要协调的元素数组。

function reconcileChildren(wipFiber, elements) {
  let index = 0
  let oldFiber =
    wipFiber.alternate && wipFiber.alternate.child
  let prevSibling = nullwhile (
    index < elements.length ||
    oldFiber != null
  ) {
    const element = elements[index]
    let newFiber = null// TODO compare oldFiber to elementif (oldFiber) {
      oldFiber = oldFiber.sibling
    }
​
    if (index === 0) {
      wipFiber.child = newFiber
    } else if (element) {
      prevSibling.sibling = newFiber
    }
  }
}

如果我们忽略掉同时遍历数组和链表所需的所有模板代码,我们就只剩下 while 循环中最核心的部分:oldFiber 和 element。element 是我们想要渲染到 DOM 中的内容,而 oldFiber 是我们上次渲染的内容。

我们需要比较它们,看看是否需要对 DOM 应用任何更改。

为了比较它们,我们使用类型:

  • 如果旧的 fiber 和新的 element 具有相同的类型,我们可以保留 DOM 节点,只用新的 props 更新它
  • 如果类型不同并且有新元素,则意味着我们需要创建一个新的 DOM 节点
  • 如果类型不同并且存在旧 fiber,则需要删除旧节点
function reconcileChildren(wipFiber, elements) {
  let index = 0
  let oldFiber =
    wipFiber.alternate && wipFiber.alternate.child
  let prevSibling = nullwhile (
    index < elements.length ||
    oldFiber != null
  ) {
    const element = elements[index]
    let newFiber = null// 新增
    const sameType =
      oldFiber &&
      element &&
      element.type == oldFiber.typeif (sameType) {
      // TODO update the node
    }
    if (element && !sameType) {
      // TODO add this node
    }
    if (oldFiber && !sameType) {
      // TODO delete the oldFiber's node
    }
​
    if (oldFiber) {
      oldFiber = oldFiber.sibling
    }
​
    if (index === 0) {
      wipFiber.child = newFiber
    } else if (element) {
      prevSibling.sibling = newFiber
    }
  }
}

这里 React 也使用了 key,这可以更好地协调。例如,它会检测子项何时更改元素数组中的位置。

当旧 fiber 和 element 具有相同的类型时,我们创建一个新的 fiber,将 DOM 节点与旧 fiber 保持一致,并从 props 从元素中保留 props。

我们还向 fiber 添加了一个新属性:effectTag。我们稍后将在提交阶段使用此属性。

const sameType =
  oldFiber &&
  element &&
  element.type == oldFiber.typeif (sameType) {
  newFiber = {
    type: oldFiber.type,
    props: element.props,
    dom: oldFiber.dom,
    parent: wipFiber,
    alternate: oldFiber,
    effectTag: "UPDATE",
  }
}

然后,对于元素需要新的 DOM 节点的情况,我们使用 PLACEMENT 效果标签标记新 fiber。

if (element && !sameType) {
  newFiber = {
    type: element.type,
    props: element.props,
    dom: null,
    parent: wipFiber,
    alternate: null,
    effectTag: 'PLACEMENT'
  }
}

对于需要删除节点的情况,我们没有新的 fiber,因此我们将 effect 标签添加到旧 fiber 中。

if (oldFiber && !sameType) {
  oldFiber.effectTag = 'DELETION'
  deletions.push(oldFiber)
}

但是当我们把 fiber tree 提交到 DOM 时,我们从 work in progress 根开始做这件事,它没有旧的 fibers。

因此,我们需要一个数组来跟踪我们想要删除的节点。

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element]
    },
    alternate: currentRoot
  }

  // 新增
  deletions = []
  nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let currentRoot = null
let wipRoot = null
// 新增
let deletions = null

然后,当我们将更改提交到 DOM 时,我们还使用来自该数组的 fibers。

function commitRoot() {
  // 新增
  deletions.forEach(commitWork)

  commitWork(wipRoot.child)
  currentRoot = wipRoot
  wipRoot = null
}

现在,让我们更改 commitWork 函数来处理新的 effectTags

function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  domParent.appendChild(fiber.dom)
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

如果 fiber 具有 PLACEMENT 效果标签,我们将像以前一样,将 DOM 节点附加到父 fiber 中的节点。

function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const domParent = fiber.parent.dom
  if (
    fiber.effectTag === "PLACEMENT" &&
    fiber.dom != null
  ) {
    domParent.appendChild(fiber.dom)
  }
​
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

如果是 DELETION,我们反其道而行之,删除 child。

if (fiber.effectTag === 'PLACEMENT' && fiber.dom != null) {
  domParent.appendChild(fiber.dom)
} else if (fiber.effectTag === 'DELETION') {
  domParent.removeChild(fiber.dom)
}

如果是 UPDATE,我们需要用更改的 props 更新现有的 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)
}

我们将在这个 updateDom 函数中执行此操作。

我们将旧 fiber 的 props 与新 fiber 的 props 进行比较,删除消失的 props,并设置新的或更改的 props。

const isProperty = key => key !== "children"
const isNew = (prev, next) => key =>
  prev[key] !== next[key]
const isGone = (prev, next) => key => !(key in next)
function updateDom(dom, prevProps, nextProps) {
  // 移除旧的属性
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach(name => {
      dom[name] = ""
    })
​
  // 设置新的或更改的属性
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      dom[name] = nextProps[name]
    })
}

我们需要更新的一种特殊类型的 prop 是事件监听器,因此如果 prop 名称以 “on” 前缀开头,我们将以不同的方式处理它们。

const isEvent = (key) => key.startsWith('on')
const isProperty = (key) => key !== 'children' && !isEvent(key)

如果事件处理程序发生更改,我们会将其从节点中删除。

function updateDom(dom, prevProps, nextProps) {
  //删除事件
  Object.keys(prevProps)
    .filter(isEvent)
    .filter(
      key =>
        !(key in nextProps) ||
        isNew(prevProps, nextProps)(key)
    )
    .forEach(name => {
      const eventType = name
        .toLowerCase()
        .substring(2)
      dom.removeEventListener(
        eventType,
        prevProps[name]
      )
    })

    // 省略之前代码
​}

然后我们添加新的处理程序。

//新增事件
Object.keys(nextProps)
  .filter(isEvent)
  .filter(isNew(prevProps, nextProps))
  .forEach((name) => {
    const eventType = name.toLowerCase().substring(2)
    dom.addEventListener(eventType, nextProps[name])
  })

codesandbox 上尝试具有 reconciliation 功能的版本。

步骤 6 完整代码

步骤 7 Function Components

接下来我们需要添加的是 function components 的支持。

首先,让我们改变一下例子。我们将使用这个简单的函数组件,它返回一个 h1 元素。

/** @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)

请注意,如果我们将 jsx 转换为 js,它将是:

function App(props) {
  return Didact.createElement('h1', null, 'Hi ', props.name)
}

const element = Didact.createElement(App, {
  name: 'foo'
})

函数组件在两个方面有所不同:

  • 来自函数组件的 fiber 没有 DOM 节点
  • 子项来自运行函数,而不是直接从 props 获取它们
function performUnitOfWork(fiber) {
  // 要改动的代码

  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }
​
  const elements = fiber.props.children
  reconcileChildren(fiber, elements)

  // 省略之前的代码
​}

我们检查 fiber type 是否是一个函数,并根据它我们转到不同的 update 函数。

updateHostComponent 中,我们执行与以前相同的操作。

function performUnitOfWork(fiber) {
  const isFunctionComponent =
    fiber.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)
}

在 updateFunctionComponent 中,我们运行函数来获取子项

对于我们的示例,这里的 fiber.typeApp 函数,当我们运行它时,它返回 h1 元素。

然后,一旦我们有了孩子,reconciliation 就会以同样的方式进行,我们不需要在那里做任何改变。

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

我们需要更改的是 commitWork 函数。

function commitWork(fiber) {
  if (!fiber) {
    return
  }
​
  const domParent = fiber.parent.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)
  }
​
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

现在我们有了没有 DOM 节点的 fiber,我们需要改变两件事。

首先,要找到 DOM 节点的父节点,我们需要沿着 fiber 树向上走,直到找到具有 DOM 节点的 fiber。

function commitWork(fiber) {
  if (!fiber) {
    return
  }
​
  // 新增
  let domParentFiber = fiber.parent
  while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent
  }
  const domParent = domParentFiber.domif (
    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") {
    // 新增
    commitDeletion(fiber, domParent)
  }
​
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

当删除一个节点时,我们还需要继续前进,直到找到一个具有 DOM 节点的子节点。

function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child, domParent)
  }
}

步骤 8 Hooks

最后一步。现在我们有了函数组件,让我们也添加 state。

让我们将示例更改为经典的 counter 组件。每次我们单击它时,它都会将状态增加 1。

请注意,我们使用 Didact.useState 来获取和更新 counter 值。

const Didact = {
  createElement,
  render,
  useState,
}
​
/** @jsx Didact.createElement */
function Counter() {
  const [state, setState] = Didact.useState(1)
  return (
    <h1 onClick={() => setState(c => c + 1)}>
      Count: {state}
    </h1>
  )
}
const element = <Counter />
const container = document.getElementById("root")
Didact.render(element, container)

这是我们从示例中调用 Counter 函数的地方。在这个函数中,我们称之为 useState

function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}
​
function useState(initial) {
  // TODO
}

我们需要在调用函数组件之前初始化一些全局变量,以便我们可以在 useState 函数中使用它们。

首先,我们设置正在进行的 fiber 。

我们还向 fiber 添加了一个 hooks 数组,以支持在同一组件中多次调用 useState。我们跟踪当前的 hook 索引。

let wipFiber = null
let hookIndex = nullfunction updateFunctionComponent(fiber) {
  wipFiber = fiber
  hookIndex = 0
  wipFiber.hooks = []
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}
​
function useState(initial) {
  // TODO
}

当函数组件调用 useState 时,我们检查是否有旧的 hook。我们使用 hook 索引检查 fiber 的 alternate 。

如果我们有一个旧的 hook,我们将 state 从旧的 hook 复制到新的 hook 上,如果没有,我们初始化 state。

然后我们将新钩子添加到 fiber 中,将钩子索引增加 1,并返回状态。

function useState(initial) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex]
  const hook = {
    state: oldHook ? oldHook.state : initial,
  }
​
  wipFiber.hooks.push(hook)
  hookIndex++
  return [hook.state]
}

useState 还应该返回一个函数来更新 state,因此我们定义了一个接收 action 的 setState 函数(对于 Counter 示例,这个 action 是将 state 递增 1 的函数)。

我们将该操作推送到我们添加到 hook 的队列中。

然后我们做一些类似于我们在 render 函数中所做的事情,将一个新的正在进行的工作根设置为下一个工作单元,以便工作循环可以开始新的渲染阶段。

function useState(initial) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex]
  const hook = {
    state: oldHook ? oldHook.state : initial,
    queue: [],
  }
​
  const setState = action => {
    hook.queue.push(action)
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot,
    }
    nextUnitOfWork = wipRoot
    deletions = []
  }
​
  wipFiber.hooks.push(hook)
  hookIndex++
  return [hook.state, setState]
}

但我们尚未运行该操作。

我们下次渲染组件时执行此操作,从旧的 hook 队列中获取所有 action,然后将它们一一应用到新的 hook 状态,因此当我们返回状态时,它会被更新。

function useState(initial) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex]
  const hook = {
    state: oldHook ? oldHook.state : initial,
    queue: [],
  }
​
  const actions = oldHook ? oldHook.queue : []
  actions.forEach(action => {
    hook.state = action(hook.state)
  })
​
  const setState = action => {
    hook.queue.push(action)
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot,
    }
    nextUnitOfWork = wipRoot
    deletions = []
  }
​
  wipFiber.hooks.push(hook)
  hookIndex++
  return [hook.state, setState]
}

就这样。我们已经构建了自己的 React 版本。

您可以在 codesandboxgithub 上使用它。

结语

除了帮助你理解 React 的工作原理外,这篇文章的目标之一是让你更容易更深入地了解 React 代码库。这就是为什么我们几乎在所有地方都使用相同的变量和函数名称。

例如,如果你在真实 React 应用程序的一个函数组件中添加了一个断点,调用堆栈应该向你显示:

  • workLoop workLoop 工作循环
  • performUnitOfWork
  • updateFunctionComponent

我们没有包含很多 React 功能和优化。例如,以下是 React 的不同之处:

  • 在 Didact 中,我们在渲染阶段遍历整个树。相反,React 遵循一些提示和启发式方法来跳过没有任何变化的整个子树。
  • 我们还在 commit 阶段遍历整个 tree。React 保留一个链表,其中仅包含具有 effect 的 fibers,并且只访问这些 fiber。
  • 每次我们构建一个新的 work in progress 树时,我们都会为每个 fiber 创建新的对象。React 回收了之前树的 fiber。
  • 当 Didact 在渲染阶段收到新的更新时,它会丢弃正在进行的工作树并从根重新开始。React 使用过期时间戳标记每个更新,并使用它来决定哪个更新具有更高的优先级。
  • 还有更多...

您还可以轻松添加一些功能:

  • 为 style 属性使用对象
  • 展平子数组
  • useEffect 钩子
  • 根据 key 做 reconciliation

如果您向 Didact 添加了这些或其他功能中的任何一个,请向 GitHub 存储库发送拉取请求,以便其他人可以看到它。

感谢阅读!