mini-react(二)实现任务调度器&fiber架构

上节我们使用递归的方式完成了将虚拟dom树转换为真实节点,但如果dom树的子节点过多时,浏览器就会出现卡顿。

原因是JS是单线程的,我们执行的任务过多,阻塞了渲染引擎,导致出现卡顿。

所以我们需要对任务进行拆分,JS 执行一部分,然后渲染引擎渲染一部分,完成之后,JS 再继续执行,渲染引擎再渲染。

浏览器给我们提供了一个钩子函数 requestIdleCallback 在空余时间执行我们想要的逻辑

requestIdleCallback

requestIdleCallback(callback[, options])

callback 是需要执行的任务,接收一个 IdleDeadline 对象作为参数。IdleDeadline 包含 2 个重要字段

  1. didTimeout,布尔值,表示任务是否超时
  2. timeRemaining() ,用于获取当前帧的剩余时间

options 是一个可选参数,目前只有一个值 timeout,表示如果超过这个时间,任务还没有执行,则强制执行任务,不需要等待空闲时间。

实现任务调度器

我们将大任务拆为多个task,再利用 requestIdleCallback 在浏览器空闲时去执行每个task,这样就可以实现边执行JS边渲染的效果了

我们定义一个变量 shouldYield 来控制是否去执行task,在空闲时间不足时跳过任务的执行

js 复制代码
let taskId = 0;
function workLoop(deadline) {
  taskId++;
  let shouldYield = false;
  while (!shouldYield) {
    if (deadline.timeRemaining() < 0) {
      shouldYield = true;
    }
    //模拟task的执行
    console.log(`run task${taskId}`);
  }
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

实现fiber架构

首先我们定义一个变量代表我们的task:let nextWorkOfUnit = null;

然后定义执行任务的函数,参数为task,它返回下一个需要执行的task:

function performWorkOfUnit(fiber) {}

这样便可以使用任务调度器去代替之前的递归操作了:

js 复制代码
function workLoop(deadline) {
  let shouldYield = false;
  while (!shouldYield && nextWorkOfUnit) {
    shouldYield = deadline.timeRemaining() < 1;
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);
  }
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

接下来我们去实现 performWorkOfUnit:

首先肯定是要执行上节的render中的逻辑,将构建的 vdom 转换为真实dom,即创建dom和处理props,我们将这两个步骤抽离出来:

js 复制代码
function createDom(type) {
  return type === "TEXT_ELEMENT"
    ? document.createTextNode("")
    : document.createElement(type);
}

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

performWorkOfUnit需要返回下一个执行的task,那我们该如何去知道下一个任务该执行什么呢?这里便需要我们将dom树去转换为链表,再通过链表去寻找下一个任务

定义链表的规则为:

  1. 子节点child
  2. 兄弟节点sibling
  3. 叔叔节点parent.sibling

a --> b --> d --> e --> c --> f --> g --> 结束

我们也将这一步抽离为单独的函数:

js 复制代码
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 = child;
  });
}

为了不破坏虚拟dom的结构,我们定义了一个新的对象newFiber,其dom属性为append的位置,同时定义变量prevChild来保存上一个子节点的内容,用来设置sibling的指向

js 复制代码
function performWorkOfUnit(fiber) {
  if (!fiber.dom) {
    //创建dom,设置添加的位置
    const dom = (fiber.dom = createDom(fiber.type));
    //添加dom
    fiber.parent.dom.append(dom);
    //处理props
    updateProps(dom, fiber.props);
  }

  //转换链表
  initChildren(fiber);

  //返回下一个任务 :1.child 2.sibling 3.parent.sibling
  return fiber.child
    ? fiber.child
    : fiber.sibling
    ? fiber.sibling
    : fiber.parent.sibling
    ? fiber.parent.sibling
    : null;
}

最后在render函数中,初始化变量nextWorkOfUnit,这样我们的fiber架构就实现完成了

function 复制代码
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el],
    },
  };
}

代码参考链接 : mini-react

参考文章

相关推荐
AskSky1 分钟前
为了搞一个完美的健身APP,我真是费尽心机
前端
斯~内克6 分钟前
基于Vue.js和PDF-Lib的条形码生成与批量打印方案
前端·vue.js·pdf
阴阳怪气乌托邦7 分钟前
别再啃OA代码了!低代码"搭积木"式搞数智化,我直接少写500行
前端·低代码
beelan12 分钟前
v-on的思考
前端
山河木马14 分钟前
前端学习C++之:.h(.hpp)与.cpp文件
前端·javascript·c++
用户92724725021915 分钟前
PHP + CSS + JS + JSON 数据采集与展示系统,支持伪静态
前端
努力只为躺平19 分钟前
一文搞懂 Promise 并发控制:批量执行 vs 最大并发数,实用场景全解析!
前端·javascript
李大玄21 分钟前
Google浏览器拓展工具 "GU"->google Utils
前端·javascript·github
爱编程的喵21 分钟前
从DOM0到事件委托:揭秘JavaScript事件机制的性能密码
前端·javascript·dom
蓝倾27 分钟前
京东批量获取商品SKU操作指南
前端·后端·api