七天快速学完mini-react ,再也不担心不会原理了(第二天)

# 七天快速学完mini-react ,再也不担心不会原理了(第一天)

第二天:任务调度器 & fiber 架构

实现任务调度器

问题:为什么需要任务调度器?

原因:当我们节点数量非常大的时候,浏览器渲染会非常卡顿,因为浏览器是单线程的

怎么解决:分层思想,拆分每个任务,每个任务只执行两个任务

我们通过requestIdleCallback这个函数,有一个参数叫deadline,它代表的是该任务下剩余的时间,通过这个我们可以来去实现任务调度器

js 复制代码
function workLoop(deadline) {
  console.log("deadline", deadline.timeRemaining())
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

这里就是简单的任务调度器,当剩余时间小于1的时候,我们就执行下个任务

js 复制代码
function workLoop(deadline) {
  console.log("deadline", deadline.timeRemaining())

  let shouldRun = false
  while (!shouldRun) {
    // 执行Dom
    shouldRun = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

实现fiber架构

首先认识一下什么是fiber架构:

Fiber 架构是一种用于构建用户界面的 React 应用程序的新架构。它是 React 16 版本中引入的一项重要功能。在传统的 React 架构中,React 使用了一种称为"协调"(Reconciliation)的机制来处理组件的更新和渲染。这种机制是基于递归的,意味着 React 会从根组件开始递归地遍历整个组件树,以确定哪些组件需要更新,并最终进行渲染。这种递归的算法在处理大型组件树或复杂的交互式用户界面时可能会导致性能问题。

Fiber 架构的目标是改进 React 的协调机制,以提高性能和用户体验。它引入了一种新的数据结构,称为 FiberFiber 是一个轻量级的 JavaScript 对象,用于表示组件树中的每个组件和其相关的信息。

Fiber 架构使用了一种称为"时间切片"(Time Slicing)的技术,将组件的更新工作分解为多个小任务,并使用优先级调度算法来决定哪些任务应该优先执行。这样可以使 React 在处理大型组件树时更加灵活和高效,提高了应用程序的响应能力和性能。

通过引入 Fiber 架构,React 可以在每个任务之间进行中断和恢复,从而实现更好的并发和交互式体验。它还为 React 引入了一些新的功能,例如异步渲染、增量渲染和错误边界等。

总的来说,Fiber 架构是 React 的一种新的渲染引擎,旨在提高性能、并发能力和用户体验。它是 React 生态系统中的重要进步之一,为构建现代 Web 应用程序提供了更好的基础。

如何实现呢?

首先节点我们可以当成一个树结构,我们需要做的是把树结构转化成链表结构,我们才好去处理

如下图,查找节点的时候,我们可以认为是孩子,兄弟,以及叔叔 ,就可以按照下面来进行的话,就是a-b-d-e-c-f-g

接下来我们就来完成这个方法吧,我们在原来的基础上进行修改

首先我们把render方法改一下,用特殊的结构存一下

js 复制代码
// 当前的任务
let nextWork = null

function render(el, container) {
  nextWork = {
    dom: container,
    props: {
      children: [el],
    },
  }
}

然后我们需要把我们实现的任务调度器安排上,这里需要执行的performWorkOfUnit函数,就是一会儿我们需要实现的具体转换方法,在执行的时候我们需要判断一下nextWork是否有值才行,并且返回的节点也需要重新赋值一下

js 复制代码
function workLoop(deadline) {
  let shouldRun = false
  while (!shouldRun && nextWork) {
    // 执行Dom
    nextWork = performWorkOfUnit(nextWork)
    console.log("", nextWork)

    shouldRun = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

接下来我们就来实现这个方法performWorkOfUnit

js 复制代码
function performWorkOfUnit(work) {
  if (!work.dom) {
    // 1.创建 DOM
    const dom = (work.dom =
      work.type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(work.type))
    work.parent.dom.append(dom)
    // 2.处理 props

    // 设置id和class
    Object.keys(work.props).forEach(key => {
      if (key !== "children") {
        // 给DOM创建props
        dom[key] = work.props[key]
      }
    })
  }
  // 3.处理节点之间的关系
  const children = work.props.children
  let prevChild = null
  children.forEach((child, index) => {
    const newWork = {
      type: child.type,
      props: child.props,
      child: null,
      parent: work,
      sibling: null,
      dom: null,
    }
    if (index === 0) {
      work.child = newWork
    } else {
      prevChild.sibling = newWork
    }
    prevChild = newWork
  })
  // 4.返回下一个任务

  if (work.child) {
    return work.child
  }
  if (work.sibling) {
    return work.sibling
  }
  return work.parent?.sibling
}

这个方法是其实就是一个用于构建虚拟DOM树的函数。它接收一个表示工作单元的对象作为参数,并根据该工作单元的类型和属性创建相应的DOM元素。如果工作单元已经具有DOM元素,则跳过创建DOM的步骤。

接下来,它处理工作单元之间的关系,将它们连接成一个树形结构。它遍历工作单元的子节点数组,并为每个子节点创建一个新的工作单元对象,并将其链接到父节点的childsibling属性上。

最后,它返回下一个要处理的工作单元。如果当前工作单元有子节点,则返回第一个子节点。如果当前工作单元有兄弟节点,则返回兄弟节点。如果当前工作单元既没有子节点也没有兄弟节点,则返回父节点的兄弟节点(如果有)。

我们看看最后创建的节点是什么?

可以看到,基本上的属性是都存在的,并且更好的表现出来了节点树之间的关系

到现在,我们就简单的完成了它们之间关系的转换,接下来我们对整体代码进行优化一下,拆分一下

js 复制代码
function createTextNode(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child => {
        return typeof child === "string" ? createTextNode(child) : child
      }),
    },
  }
}

function render(el, container) {
  nextWork = {
    dom: container,
    props: {
      children: [el],
    },
  }
}

let nextWork = null
function workLoop(deadline) {
  let shouldYield = false
  while (!shouldYield && nextWork) {
    nextWork = performWorkOfUnit(nextWork)

    shouldYield = deadline.timeRemaining() < 1
  }

  requestIdleCallback(workLoop)
}

function createDom(type) {
  return type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(type)
}

function updateProps(dom, props) {
  Object.keys(props).forEach(key => {
    if (key !== "children") {
      dom[key] = props[key]
    }
  })
}

function initChildren(fiber) {
  const children = fiber.props.children
  let prevChild = null
  children.forEach((child, index) => {
    const newFiber = {
      type: child.type,
      props: child.props,
      child: null,
      parent: fiber,
      sibling: null,
      dom: null,
    }

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

function performWorkOfUnit(fiber) {
  if (!fiber.dom) {
    const dom = (fiber.dom = createDom(fiber.type))

    fiber.parent.dom.append(dom)

    updateProps(dom, fiber.props)
  }

  initChildren(fiber)

  // 4. 返回下一个要执行的任务
  if (fiber.child) {
    return fiber.child
  }

  if (fiber.sibling) {
    return fiber.sibling
  }

  return fiber.parent?.sibling
}

requestIdleCallback(workLoop)

const React = {
  render,
  createElement,
}

export default React

我们所说的work其实就是fiber架构,这就是优化后的版本。

大家可以好好理解一下这个转化的过程。第二天的内容就到此为止啦!

相关推荐
栈老师不回家16 分钟前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙22 分钟前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠26 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
阿伟来咯~2 小时前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓3 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js