实现一个 Mini React:核心功能详解 - fiber 的迷你版实现 (1)

xdm,又要到饭了,又更新代码了!

总结一下上一篇完成的内容,

  1. 实现了mini diff 算法

有兴趣的可以点这里查看diff 算法的迷你版实现

这篇将会实现一个fiber 的迷你版本。这里,简单介绍一下fiber 的功能和解决的问题。

Fiber 将整个渲染过程(包括协调和提交阶段)拆分为多个小任务,每个小任务代表渲染工作的一部分。这些小任务可以在每个时间切片内执行,确保主线程不会被长时间占用。这个优势在复杂组件 / 资源消耗重的场景优势特别明显。它的出现也特别针对这样的场景提出的解决方法。

那么根据上面的定义,我们需要完成

  1. 渲染过程拆分多个小任务,每一个小任务渲染一部分工作
  2. 任务需要可以按照等级的高低,调整任务的执行顺序(优先级)
  3. 需要配合帧或者刷新率来调度,如果当前的frame 没有空闲,则需要等待下一个frame 完成剩余任务。
  4. 调度器需要记录当前任务的运行状态, 可以中断/恢复上一次剩余的任务。

Mini Fiber 的实现

1.1 优先级常量定义

js 复制代码
const PRIORITY = {
  IMMEDIATE: 1,    // 最高优先级:立即执行
  USER_BLOCKING: 2, // 用户交互任务,尽快执行
  NORMAL: 3,       // 普通任务
  LOW: 4,          // 低优先级任务
  IDLE: 5          // 闲置时才执行的任务
};
  1. 调度器核心数据结构
js 复制代码
let taskQueue = []; // 存储待执行的任务
let currentTask = null; // 当前正在执行的任务
let isPerformingTasks = false; // 标记任务是否在执行
  1. 调度函数
js 复制代码
function schedule(task, priority = PRIORITY.NORMAL) {
  taskQueue.push({ task, priority, progress: 0 }); // 添加任务到队列
  taskQueue.sort((a, b) => a.priority - b.priority); // 按优先级排序
  requestHostCallback(); // 请求执行任务
}

function requestHostCallback() {
  if (!isPerformingTasks) {
    requestIdleCallback(performTasks); // 在浏览器空闲时执行任务
  }
}
  1. 执行任务的函数
js 复制代码
function performTasks(deadline) {
  isPerformingTasks = true;

  while (taskQueue.length > 0 && deadline.timeRemaining() > 0) {
    if (currentTask === null) {
      currentTask = taskQueue.shift(); // 获取下一个任务
    }

    const { taskFn, priority, progress } = currentTask;
    const chunkDuration = PRIORITY[priority] <= PRIORITY.USER_BLOCKING ? 10 : 5; // 高优先级任务可以获取更多的分片完成任务。

    const taskStartTime = performance.now();
    let isTaskCompleted = false;

    // 执行任务的一部分
    while (performance.now() - taskStartTime < chunkDuration) {
      isTaskCompleted = taskFn(currentTask.progress); // 执行任务并更新进度
      currentTask.progress++;

      if (isTaskCompleted) break; // 任务完成
    }

    if (isTaskCompleted) {
      currentTask = null; // 任务完成,重置当前任务
    } else {
      // 任务未完成,等待下一次执行
      break;
    }
  }

  // 如果还有任务未完成,则继续调度
  if (taskQueue.length > 0 || currentTask !== null) {
    requestIdleCallback(performTasks);
  } else {
    isPerformingTasks = false; // 所有任务已完成
  }
}
  1. 修改 setState 使用调度器
js 复制代码
function setStateWithScheduler(newState) {
  schedule(() => {
    setState(newState); // 假设已有 setState 实现
  }, PRIORITY.USER_BLOCKING); // 用户交互任务优先级
}
  1. 处理长任务的示例
js 复制代码
function longTask(progress) {
  console.log(`Progress: ${progress.current} / ${progress.total}`);

  // 模拟执行任务的一部分
  progress.current++;

  // 判断任务是否完成
  return progress.current >= progress.total;
}

// 初始化任务进度
const longTaskProgress = { current: 0, total: 10 };

// 调度长任务
schedule(() => longTask(longTaskProgress), PRIORITY.NORMAL);

解释与优化

1. 任务拆分与进度管理

executeTask 函数中,每个任务维护自己的 progress 状态。这允许任务在每次执行时只完成一小部分,并在下次空闲时间继续执行剩余部分。这样,长任务不会阻塞主线程,可以被拆分为多个小任务块逐步完成。(这个对应实现了我们一开始设定需要完成的第1条)

2. 优先级排序

任务队列在每次添加新任务后会根据优先级重新排序,确保高优先级任务优先执行。这模仿了 React Fiber 的优先级调度机制,保证用户交互任务能够快速响应。(这个对应实现了我们一开始设定需要完成的第2条)

3. 时间切片与任务执行

通过为不同优先级的任务分配不同的 chunkDuration,高优先级任务可以在较长的时间切片内完成更多工作,而低优先级任务则分配较短的时间切片。这种策略帮助保持应用的响应性。(这个对应实现了我们一开始设定需要完成的第3条)

4. 避免无限递归

使用 isPerformingTasks 标志位来确保在任务执行过程中不会重复调用 requestIdleCallback,避免陷入无限递归调用,确保调度器的稳定性。

React 是否使用 requestIdleCallback

requestIdleCallback 的使用

  • React Fiber 本身并没有直接依赖 requestIdleCallback 来实现任务调度。
  • React 的内部调度器(如 scheduler 包)更倾向于使用一种更通用和灵活的方法 ,结合 requestIdleCallback 及其替代方案,以确保在各种浏览器环境中都能高效运行。

兼容性问题

  • 浏览器支持不一致

    • requestIdleCallback 在某些浏览器中支持不佳或尚未完全支持(如早期的 Safari 版本)。
  • 可靠性与精确性

    • requestIdleCallback 的调用时间和执行环境在不同浏览器中可能有所差异,影响任务调度的一致性。

React 的解决方案

为了应对 requestIdleCallback 的兼容性问题,React 的调度器通常采用以下策略:

  1. 条件使用 requestIdleCallback

    • 当浏览器支持 requestIdleCallback 时,优先使用它来执行低优先级任务。
  2. 提供回退方案

    • requestIdleCallback 不可用的环境中,使用 setTimeoutrequestAnimationFrame 作为替代。
  3. 灵活的调度逻辑

    • 根据当前浏览器环境动态选择最佳的调度方法,确保任务调度的高效性和一致性。

这一篇实现了一个迷你版本的fiber,也大致实现了我们一开始设定需要完成的4个点,

  1. 渲染过程拆分多个小任务,每一个小任务渲染一部分工作
  2. 任务需要可以按照等级的高低,调整任务的执行顺序(优先级)
  3. 需要配合帧或者刷新率来调度,如果当前的frame 没有空闲,则需要等待下一个frame 完成剩余任务。
  4. 调度器需要记录当前任务的运行状态, 可以中断/恢复上一次剩余的任务。

我们下一篇会引入fiber 树的概念。这一篇实现了一个简单版本fiber,让你对fiber 有一个基本的了解,大致解决了什么问题,以及使用什么方法解决问题。

下一篇,我们将继续完成实现迷你版本的fiber 架构以帮助我们增强其原理的理解。

如果这样的长度/强度你觉得可以接受,觉得有帮助,可以继续阅读下一篇,实现一个 Mini React:核心功能详解 - fiber 的迷你版实现(2)。

如果文章对你有帮助,请点个赞支持一下!

啥也不是,散会。

相关推荐
吕彬-前端15 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱18 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai27 分钟前
uniapp
前端·javascript·vue.js·uni-app
bysking1 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb