React- fiber架构 和 diff算法

导言

这篇文章主要是为了学习和记录。

讲了react的事件机制 ,diff算法 以及 16.8引入的 fiber架构

react合成事件详情解释

如果dom上绑定了过多的事件处理函数,整个页面响应以及内存可能会有影响。 react为了避免这类DOM事件的滥用,实现了一个中间层。

在document处监听所有支持的事件,当事件发生并冒泡到document上时,交给中间层。

当事件触发时,使用统一的dispatchEvent将指定函数执行

javascript 复制代码
export default class Test extends Component {
    constructor() {
        super(arguments);
        this.onReactClick.bind(this);
    }
    componentDidMount() {
        const parentDom = ReactDOM.findDOMNode(this);
        const childrenDom = parentDom.querySelector(".button");
        childrenDom.addEventListener('click', this.onDomClick, false);
    }
    onDomClick() {  // 事件委托
        console.log('Javascript Dom click');
    }
    onReactClick() {  // react合成事件
        console.log('React click');
    }
    render() {
        return (
            <div>
                <button className="button" onClick={this.onReactClick}>点击</button>
            </div>
        )
    }
}

合成事件和原生事件混合使用

  • 响应顺序

addEventListner

重点是addEventListner的第三个参数

  • 默认是false

事件冒泡和事件捕获

HTML DOM中的事件有两种传播方式,即冒泡和捕获。事件传播是一种在事件发生时定义元素顺序的方法。

  • 冒泡: 即从里到外
  • 捕获:即从外到里

addEventListner的第三个参数设置为true,表示使用捕获捕获

react16.8的 diff算法

虚拟DOM

虚拟DOM的本质是一个对象。由于操作真实DOM的成本比较高,所以推出了虚拟DOM。

在页面结果需要修改时,先修改虚拟DOM的节点,然后再对比 新旧两棵DOM树的差异。

最后把差异patch到真实DOM树上。

另外,也正是 基于虚拟DOM,react实现了自己的一套事件机制,并不少在事件绑定在一个确定的节点上,而是利用事件冒泡的过程,采用事件代理,批量更新的方式,从而也磨皮了各个浏览器之间的事件兼容性问题。

传统的diff算法

在计算一棵树转为另一课树中有哪些变化,传统的diff算法通过循环递归对比节点的方式,依次对比,其时间复杂度是 O(n^3)

举个例子:

js 复制代码
function diffTrees(oldTree, newTree) {
  const differences = [];

  function diffNodes(oldNode, newNode, path) {
    if (oldNode === newNode) {
      // 节点完全相同,无需处理
      return;
    }

    if (!oldNode && newNode) {
      // 新增节点
      differences.push({ type: "ADDED", path, node: newNode });
      return;
    }

    if (oldNode && !newNode) {
      // 删除节点
      differences.push({ type: "REMOVED", path, node: oldNode });
      return;
    }

    // 节点内容改变
    if (typeof oldNode === "object" && typeof newNode === "object") {
      // 假设节点是对象,并且我们关心的是它们的属性差异
      const oldKeys = Object.keys(oldNode);
      const newKeys = Object.keys(newNode);

      // 找出属性增加、删除或修改的情况
      for (const key of oldKeys) {
        if (!newNode.hasOwnProperty(key)) {
          // 属性被删除
          differences.push({
            type: "REMOVED_ATTR",
            path: [...path, key],
            oldValue: oldNode[key],
          });
        } else {
          // 递归对比属性值
          diffNodes(oldNode[key], newNode[key], [...path, key]);
        }
      }

      for (const key of newKeys) {
        if (!oldNode.hasOwnProperty(key)) {
          // 属性被添加
          differences.push({
            type: "ADDED_ATTR",
            path: [...path, key],
            newValue: newNode[key],
          });
        }
      }
    } else {
      // 节点内容直接改变(不是对象)
      differences.push({
        type: "CHANGED",
        path,
        oldValue: oldNode,
        newValue: newNode,
      });
    }
  }

  // 开始对比树的根节点
  diffNodes(oldTree, newTree, []);

  return differences;
}

// 示例
const oldTree = {
  name: "Old Root",
  children: [{ name: "Old Child 1" }, { name: "Old Child 2", age: 5 }],
};

const newTree = {
  name: "Root", // 名称变化
  children: [
    { name: "Old Child 1", age: 10 }, // 添加年龄属性
    { name: "Updated Child 2", age: 6 }, // 名称变化
    { name: "New Child 3" }, // 新增节点
  ],
};

const differences = diffTrees(oldTree, newTree);
console.log(differences);

react 的diff算法

O(n^ 3) 转化为O(n)

react 通过三大策略完成了优化

  1. 同级比较

由于dom节点跨层级移动的操作比较少,在对比的过程中,如果发现节点不在了,会完全删除,然后重新创建。

  1. 组件比较

组件比较有两种策略

  • 相同类型组件

  • 不同类型组件

  • 相同类型组件

组件类型没有改变,但属性改变了,react会更新该组件的属性,并调用render方法

  • 不同类型组件

旧的组件会被卸载,新的组件会被挂载

  1. 节点比较

对于同一层级的一子自节点,通过唯一的key进行比较

为什么不推荐使用 index作为 key值呢?

例如:当用 index作为key时

当数组中间插入,或者数组重新排序后,下标对应的组件就改了,那么key也更改,基本所有的节点都必须重新渲染一遍。

使用id,唯一值才是正确的。

react fiber架构

为什么要推出 react fiber呢 ?

得先了解一下一些基本知识

进程与线程

浏览器的一个tab(标签页)是一个进程。其中包含了多个线程。例如

  • GUI渲染线程:主要负责渲染浏览器页面。HTML和 CSS的解析,构建DOM树和 CSSOM树。当页面需要重绘 或者回流时,该线程就会执行。 值得注意的是,GUI渲染线程和 js执行线程是互斥的。

还有一点:

会阻塞GUI渲染线程的是javaScript的同步任务(例如递归,循环,计算密集型任务)

而使用处理异步任务时(如:promise, async/await ),会挂载到异步任务队列中, 并不会阻塞渲染。

  • js执行线程。负责处理js脚本。

react 和 vue的响应式原理

在react中, 组件的状态是不能被修改的。 setState没有修改原来那块内存中的变量,而是去新开辟一块内存。vue则是直接修改保存状态的那块原始内存。

数据修改后,react中,调用setState方法后,会自顶向下重新渲染组件,自顶向下的含义是,该组件以及它的子组件全部需要渲染。

而vue是使用了Object.defineProperty对数据设置的 setter 和 getter进行劫持。 也就是说,vue能知道哪个视图模版中用到了这个数据,然后只更新这个组件即可。

这也就意味了,react的组件渲染是很消耗性能的,因为你要对比当前组件以及子组件的虚拟DOM树。

为什么要推出 react fiber呢?

react组件更新是CPU密集的操作,因为它要做对比新旧两颗虚拟DOM树的操作,找到更新的内容,patch更新到真实DOM上。由于这种操作时通过递归方式实现的,是同步且不可中断的。所以过多节点对比,会直接影响 ui渲染,影响用户体验。

fiber是什么?

react fiber并没有让 diff算法的时间缩短,而是把 diff的过程分成了一小段一小段, 称为 fiber. 由于使用了链表这种结构,可以当有高优先级的任务执行时中断,当浏览器空闲时恢复,底层使用 reqestIdCallback API来判断浏览器是否空闲,最后直到完成diff后,一次性更新到视图上。

替代了原有同步且不可中断的递归diff方式。

相关推荐
Captaincc3 小时前
AI用量v0.1.11更新发布 新增 jusage doctor 诊断指令 托盘展示token 和余额 新增 AutoClaw 支持
前端·后端·vibecoding
计算机魔术师5 小时前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen5 小时前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒5 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow6 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8186 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800786 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明6 小时前
第 6 章 组件进阶
前端·vue.js
江华森6 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端
南青6 小时前
Vue 3 中后台实战:我踩过的 10 个坑和最佳实践
前端