0

0

React中Diff算法是什么?Diff算法的策略及实现

不言

不言

发布时间:2018-09-28 17:27:03

|

4242人浏览过

|

来源于segmentfault思否

转载

本篇文章给大家带来的内容是关于react中diff算法是什么?diff算法的策略及实现,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

1、什么是Diff算法

  • 传统Diff:diff算法即差异查找算法;对于Html DOM结构即为tree的差异查找算法;而对于计算两颗树的差异时间复杂度为O(n^3),显然成本太高,React不可能采用这种传统算法;

  • React Diff:

    • 之前说过,React采用虚拟DOM技术实现对真实DOM的映射,即React Diff算法的差异查找实质是对两个JavaScript对象的差异查找;

    • 基于三个策略:

  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。(tree diff)

  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结(component diff)

  3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。(element diff)

2、React Diff算法解读

  • 首先需要明确,只有在React更新阶段才会有Diff算法的运用;

  • React更新机制:

388197929-5b86b10366837_articlex.png

  • React Diff算法优化策略图:

3650587496-5b8772c0e2719_articlex.png

  • React更新阶段会对ReactElement类型判断而进行不同的操作;ReactElement类型包含三种即:文本、Dom、组件;

  • 每个类型的元素更新处理方式:

    • 自定义元素的更新,主要是更新render出的节点,做甩手掌柜交给render出的节点的对应component去管理更新。

    • text节点的更新很简单,直接更新文案。

    • 浏览器基本元素的更新,分为两块:

  1. 更新属性,对比出前后属性的不同,局部更新。并且处理特殊属性,比如事件绑定。

  2. 子节点的更新,子节点更新主要是找出差异对象,找差异对象的时候也会使用上面的shouldUpdateReactComponent来判断,如果是可以直接更新的就会递归调用子节点的更新,这样也会递归查找差异对象。不可直接更新的删除之前的对象或添加新的对象。之后根据差异对象操作dom元素(位置变动,删除,添加等)。

  • 事实上Diff算法只被调用于React更新阶段的DOM元素更新过程;为什么这么说?

1、 如果为更新文本类型,内容不同就直接更新替换,并不会调用复杂的Diff算法:

海螺AI
海螺AI

MiniMax平台的AI对话问答工具,你的AI伙伴

下载
 ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) {
    //与之前保存的字符串比较
    if (nextText !== this._currentElement) {
      this._currentElement = nextText;
      var nextStringText = '' + nextText;
      if (nextStringText !== this._stringText) {
        this._stringText = nextStringText;
        var commentNodes = this.getHostNode();
        // 替换文本元素
        DOMChildrenOperations.replaceDelimitedText(
          commentNodes[0],
          commentNodes[1],
          nextStringText
        );
      }
    }
  }

2、对于自定义组件元素:

class Tab extends Component {
    constructor(props) {
        super(props);
        this.state = {
            index: 1,
        }
    }
    shouldComponentUpdate() {
        ....
    }
    render() {
        return (
            

item1

item1

) } }
  • 需要明确的是,何为组件,可以说组件只不过是一段Html结构的包装容器,并且具备管理这段Html结构的状态等能力;

  • 如上述Tab组件:它的实质内容就是render函数返回的Html结构,而我们所说的Tab类就是这段Html结构的包装容器(可以理解为一个包装盒子);

  • 在React渲染机制图中可以看到,自定义组件的最后结合React Diff优化策略一(不同类的两个组件具备不同的结构)

3、基本元素:

ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) {
    var prevElement = this._currentElement;
    this._currentElement = nextElement;
    this.updateComponent(transaction, prevElement, nextElement, context);
}

ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) {
    //需要单独的更新属性
    this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);
    //再更新子节点
    this._updateDOMChildren(
      lastProps,
      nextProps,
      transaction,
      context
    );

    // ......
}
  • 在this._updateDOMChildren方法内部才调用了diff算法。

3、React中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,所以我们需要做移动的操作
        // 移动已有的子节点
        // NOTICE:这里根据nextIndex, lastIndex决定是否移动
        updates = enqueue(
          updates,
          this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
        );

        // 更新lastIndex
        lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        // 更新component的.mountIndex属性
        prevChild._mountIndex = nextIndex;

      } else {
        if (prevChild) {
          // 更新lastIndex
          lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        }

        // 添加新的子节点在指定的位置上
        updates = enqueue(
          updates,
          this._mountChildAtIndex(
            nextChild,
            mountImages[nextMountIndex],
            lastPlacedNode,
            nextIndex,
            transaction,
            context
          )
        );


        nextMountIndex++;
      }

      // 更新nextIndex
      nextIndex++;
      lastPlacedNode = ReactReconciler.getHostNode(nextChild);
    }

    // 移除掉不存在的旧子节点,和旧子节点和新子节点不同的旧子节点
    for (name in removedNodes) {
      if (removedNodes.hasOwnProperty(name)) {
        updates = enqueue(
          updates,
          this._unmountChild(prevChildren[name], removedNodes[name])
        );
      }
    }
  }

4、基于中Diff的开发建议

  • 基于tree diff:

  1. 开发组件时,注意保持DOM结构的稳定;即,尽可能少地动态操作DOM结构,尤其是移动操作。

  2. 当节点数过大或者页面更新次数过多时,页面卡顿的现象会比较明显。

  3. 这时可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。

  • 基于component diff

    1. 注意使用 shouldComponentUpdate() 来减少组件不必要的更新。

    2. 对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff的性能消耗。

  • 基于element diff

    1. 对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

    相关专题

    更多
    js获取数组长度的方法
    js获取数组长度的方法

    在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

    536

    2023.06.20

    js刷新当前页面
    js刷新当前页面

    js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

    372

    2023.07.04

    js四舍五入
    js四舍五入

    js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

    707

    2023.07.04

    js删除节点的方法
    js删除节点的方法

    js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

    470

    2023.09.01

    JavaScript转义字符
    JavaScript转义字符

    JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

    388

    2023.09.04

    js生成随机数的方法
    js生成随机数的方法

    js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

    989

    2023.09.04

    如何启用JavaScript
    如何启用JavaScript

    JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

    652

    2023.09.12

    Js中Symbol类详解
    Js中Symbol类详解

    javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

    537

    2023.09.20

    虚拟号码教程汇总
    虚拟号码教程汇总

    本专题整合了虚拟号码接收验证码相关教程,阅读下面的文章了解更多详细操作。

    25

    2025.12.25

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    Node.js 教程
    Node.js 教程

    共57课时 | 7.4万人学习

    CSS3 教程
    CSS3 教程

    共18课时 | 4万人学习

    Vue 教程
    Vue 教程

    共42课时 | 5.5万人学习

    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号