当前位置: 首页 > news >正文

一文讲清楚React的diff算法

文章目录

  • 一文讲清楚React的diff算法
  • 1. 为什么需要diff
  • 2. diff的具体策略和实现
    • 2.1 Tree Diff
    • 2.2 Component Diff
    • 2.3 Element Diff
    • 2.4 diff算法源码

一文讲清楚React的diff算法

1. 为什么需要diff

  • 首先,diff只有在更新的时候才会有,首次渲染是全量渲染,不存在对比一说
  • 那为什么更新的时候需要diff呢,我们知道React通过VDOM减少对DOM的操作,但是当我们state或者props变化的时候,不能只要有变化就重新生成整个DOM吧,我们最好是知道哪变了,变啥了,影不影响目前的渲染,要不要全部重新渲染还是是渲染部分就可以
  • diff算法就是干这个事

2. diff的具体策略和实现

  • React将传统的时间复杂度为O(n3)循环递归遍历VDOM优化为时间复杂度为O(n1)的diff算法,主要通过一下三个策略
    1. 节点的跨层级移动忽略不计,针对TreeDiff优化
    1. 相同类的两个组件会生成相似的树状结构,不同类的两个组件将会生成不同的树状结构,针对Component Diff进行优化
    1. 同一层的节点,通过key进行区分,针对Element Diff进行优化

2.1 Tree Diff

  • 只删除创建不移动
  • 简单点来说就是同一层级上,旧节点有子节点A,新节点没有,但是新节点的子节点B下面有A节点,这时候不会把旧的节点A移动到D节点下面,而是直接在B节点下面新建子节点A,并删除原来的子节点A
    在这里插入图片描述

2.2 Component Diff

  • 对比前后,如果组件的类型相同,则继续下走
  • 如果组件类型不同,删除旧组件,创建新组建
    在这里插入图片描述

2.3 Element Diff

  • Element Diff 对应三种节点操作,分别为 INSERT_MARKUP(插入)、MOVE_EXISTING(移动) 和 REMOVE_NODE(删除)。

    1. INSERT_MARKUP:新的组件类型不在旧集合中,即全新的节点,需要对新节点进行插入操作。
    1. MOVE_EXISTING:旧集合中有新组件类型,且 element 是可更新的类型,generateComponent 已调用 recevieComponent,这种情况下 prevChild = nextChild,这时候就需要做移动操作,可以复用以前的 DOM 节点。
    1. REMOVE_NODE:旧组件类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。
      在这里插入图片描述
  • index: 新集合的遍历下标。

  • oldIndex:当前节点在⽼集合中的下标

  • maxIndex:在新集合访问过的节点中,其在⽼集合的最⼤下标

  • 如果当前节点在新集合中的位置⽐⽼集合中的位置靠前的话,是不会影响后续节点操作的,这⾥这时候
    被动字节不⽤动
    操作过程中只⽐较oldIndex和maxIndex,规则如下:

  • 当oldIndex>maxIndex时,将oldIndex的值赋值给maxIndex

  • 当oldIndex=maxIndex时,不操作

  • 当oldIndex<maxIndex时,将当前节点移动到index的位置
    diff 过程如下:

  • 节点B:此时 maxIndex=0,oldIndex=1;满⾜ maxIndex< oldIndex,因此B节点不动,此时
    maxIndex= Math.max(oldIndex, maxIndex),就是1

  • 节点A:此时maxIndex=1,oldIndex=0;不满⾜maxIndex< oldIndex,因此A节点进⾏移动操作,
    此时maxIndex= Math.max(oldIndex, maxIndex),还是1

  • 节点D:此时maxIndex=1, oldIndex=3;满⾜maxIndex< oldIndex,因此D节点不动,此时
    maxIndex= Math.max(oldIndex, maxIndex),就是3

  • 节点C:此时maxIndex=3,oldIndex=2;不满⾜maxIndex< oldIndex,因此C节点进⾏移动操作,
    当前已经⽐较完了
    当ABCD节点⽐较完成后, diff 过程还没完,还会整体遍历⽼集合中节点,看有没有没⽤到的节点,
    有的话,就删除

2.4 diff算法源码

_updateChildren: function(nextNestedChildrenElements, transaction, context) {// 存储之前渲染的子元素var prevChildren = this._renderedChildren;// 存储已经移除的子元素var removedNodes = {};// 存储将要挂载的子元素var mountImages = [];// 获取新的子元素数组,并执行子元素的更新var nextChildren = this._reconcilerUpdateChildren(prevChildren,nextNestedChildrenElements,mountImages,removedNodes,transaction,context);// 如果新旧子元素都不存在,直接返回if (!nextChildren && !prevChildren) {return;}var updates = null;var name;var nextIndex = 0;var lastIndex = 0;var nextMountIndex = 0;var lastPlacedNode = null;for (name in nextChildren) {if (!nextChildren.hasOwnProperty(name)) {continue;}var prevChild = prevChildren && prevChildren[name];var nextChild = nextChildren[name];if (prevChild === nextChild) {// 同一个引用,说明是使用的同一个component,需要进行移动操作// 移动已有的子节点// 注意:这里根据nextIndex, lastIndex决定是否移动updates = enqueue(updates,this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex));// 更新lastIndexlastIndex = Math.max(prevChild._mountIndex, lastIndex);// 更新component的.mountIndex属性prevChild._mountIndex = nextIndex;} else {if (prevChild) {// 更新lastIndexlastIndex = Math.max(prevChild._mountIndex, lastIndex);}// 添加新的子节点在指定的位置上updates = enqueue(updates,this._mountChildAtIndex(nextChild,mountImages[nextMountIndex],lastPlacedNode,nextIndex,transaction,context));nextMountIndex++;}// 更新nextIndexnextIndex++;lastPlacedNode = ReactReconciler.getHostNode(nextChild);}// 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点for (name in removedNodes) {if (removedNodes.hasOwnProperty(name)) {updates = enqueue(updates,this._unmountChild(prevChildren[name], removedNodes[name]));}}
}
http://www.lryc.cn/news/582657.html

相关文章:

  • 汽车功能安全系统阶段开发【技术安全方案TSC以及安全分析】5
  • Eigen中Isometry3d的使用详解和实战示例
  • UDP的socket编程
  • 黑马点评系列问题之P37商户点评缓存作业,用了string和list两种方法,可以直接复制粘贴
  • 微软上线Deep Research:OpenAI同款智能体,o3+必应双王炸
  • 专题:2025数据资产AI价值化:安全、战略与应用报告|附400+份报告PDF、原数据表汇总下载
  • openEuler2203sp4-vg磁盘组中剔除磁盘
  • 香港站群服务器与普通香港服务器对比
  • Windows 系统安装与使用 Claude Code 全攻略
  • 【LeetCode 热题 100】142. 环形链表 II——快慢指针
  • OpenWebUI(4)源码学习-后端routers路由模块
  • 车载以太网-TC8测试-UT(Upper Tester)
  • C语言使用Protobuf进行网络通信
  • 2025年微软mos备考攻略-穷鬼版
  • claude code-- 基于Claude 4 模型的智能编程工具,重塑你的编程体验
  • 【DOCKER】-2 docker基础
  • 字符串大小比较的方式|函数的多返回值
  • python transformers库笔记(BertForTokenClassification类)
  • BM10 两个链表的第一个公共结点
  • Linux_常见指令和权限理解
  • OSPFv3与OSPFv2不同点
  • 【Spring WebSocket详解】Spring WebSocket从入门到实战
  • springboot单体项目的发布生产优化
  • 【保姆级目标检测教程】Ubuntu 20.04 部署 YOLOv13 全流程(附训练/推理代码)
  • 基于SpringBoot+Vue的非遗文化传承管理系统(websocket即时通讯、协同过滤算法、支付宝沙盒支付、可分享链接、功能量非常大)
  • 【WEB】Polar靶场 16-20题 详细笔记
  • 从0到1搭建ELK日志收集平台
  • OpenCV探索之旅:形态学魔法
  • mit6.5840-lab3-3D-SnapShot-25Summer
  • nmon使用方法