建立你自己的react(五)---Fibers

前言

自从react16.8.0版本出来fiber概念以后,这个就成为面试的重点,如果面试官问你fiber的相关问题,你是否能答得出来呢

Fibers

为了组织工作单元,我们将需要一个数据结构:一个fiber树

我们将每一个元素对应一个fiber,每一个fiber就是一个工作单元

让我们展示给你一个例子。

假设我们想要渲染一个像这样的元素树:

js 复制代码
Didact.render(
  <div>
    <h1>
      <p />
      <a />
    </h1>
    <h2 />
  </div>,
  container
)

在这个render中,我们将创建一个root fiber,然后设置他作为nextUnitOfWork. 剩余的工作将发生在performUnitOfWork函数, 我们将为每一个fiber做以下三件事情:

  1. 添加元素到DOM中
  2. 为每一个元素的子元素创建fiber
  3. 选择下一个工作单元

这个数据结构的目标之一是为了可以方柏霓的找到下一个工作单元。 这也就是为什么每一个fiber都与他的第一个孩子2,他的兄弟节点和他的父母有链接。

当我们完成了在每一个fiber上的performing work,如果它有一个子节点那么fiber将作为下一个工作单元。

从我们的例子来看,当我们完成了在div 这个fiber上的工作,那么下一个工作单元就是h1 fiber.

如果这个fiber没有一个子节点,我们会使用兄弟节点作为下一个工作单元。

例如,这个p fiber没有一个子节点,所以我们在完成它以后转移到a fiber.

如果一个fiber既没有子节点,也没有兄弟节点,那么我们就去找'叔叔':这个父母的兄弟,例如像a和h2这样的fibers

如果这个父节点没有兄弟节点,我们会继续这个父节点向上找,直到我们找到一个有兄弟的父节点,或者直到根节点。如果我们已经到达了根节点,那么也就意味着我们已经完成所有渲染的工作。

现在让我们来用代码实现。

首先,让我们移除这些代码从render函数中。

js 复制代码
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]
    })
​
  element.props.children.forEach(child =>
    render(child, dom)
  )
​
  container.appendChild(dom)
}

我们保留创建一个DOM节点这一块内容,我们稍后还会用到它。

在这个render函数中我们设置nextUnitOfWork为这个fiber树的根节点。

js 复制代码
function render(element, container) {
  nextUnitOfWork = {
    dom: container,
    props: {
      children: [element],
    },
  }
}

然后,当这个浏览器已经准备好的时候,它将调用我们的workLoop函数,我们也将从这个根部开始工作。

js 复制代码
function workLoop(deadline) {
  nextUnitOfWork = performUnitOfWork(
    nextUnitOfWork
  )
}

​
function performUnitOfWork(fiber) {
  // TODO 添加dom节点
  // TODO 创建一个新的fiber
  // TODO 返回下一个工作单元
}

首先,我们创建一个新节点,然后添加它到这个DOM中,我们保持这个DOM 节点的追踪,在这个fiber.dom属性中。

js 复制代码
function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }
​
  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
​
  // TODO 创建一个新的fiber
  // TODO 返回下一个工作单元
}

然后为每一个子节点都创建一个新的fiber。

js 复制代码
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,
    }
  }

接下来我们添加它到我们的fiber树中,也设置它作为一个子节点或者作为一个兄弟节点,依赖于是否它是第一个子元素。

js 复制代码
if (index === 0) {
    fiber.child = newFiber
  } else {
    prevSibling.sibling = newFiber
  }
​
  prevSibling = newFiber
  index++

最终我们查找下一个工作节点。我们首先尝试他的子节点,然后是兄弟节点,然后是他的叔叔,等等。

js 复制代码
if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }

这就是最终我们的performUnitOfWork 函数

js 复制代码
function performUnitOfWork(fiber) {
 if (!fiber.dom) {
   fiber.dom = createDom(fiber)
 }
​
 if (fiber.parent) {
   fiber.parent.dom.appendChild(fiber.dom)
 }
​
 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++
 }
​
 if (fiber.child) {
   return fiber.child
 }
 let nextFiber = fiber
 while (nextFiber) {
   if (nextFiber.sibling) {
     return nextFiber.sibling
   }
   nextFiber = nextFiber.parent
 }
}

参考

相关推荐
热爱编程的小曾10 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin22 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox34 分钟前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox