专栏首页小丞前端库React入门学习(四)-- diffing 算法

React入门学习(四)-- diffing 算法

📢 大家好,我是小丞同学,一名大二的前端爱好者 📢 这篇文章将尽力说明白 diff 算法 📢 愿你忠于自己,热爱生活

前言

diff 算法是 React 提升渲染性能的一种优化算法,在 React 中有着很重要的地位,也不止于 React ,在 Vue 中也有 diff 算法,似乎没有差别。在最近的 React 学习中,学到了 diff 算法,感觉视频中的内容有点浅,对 diff 算法不够深入,因此想要深入的了解以下 diff 算法。于是在掘金,知乎,CSDN 等平台上,看了大量的博客,都非常地不错,可惜看不明白,wwww。所以这篇文章只是自己对于 diff 算法的一点理解,有什么问题或者错误的地方,大家一定要指出

什么是虚拟 DOM ?

在谈 diff 算法之前,我们需要先了解虚拟 DOM 。它是一种编程概念,在这个概念里,以一种虚拟的表现形式被保存在内存中。在 React 中,render 执行的结果得到的并不是真正的 DOM 节点,而是 JavaScript 对象

虚拟 DOM 只保留了真实 DOM 节点的一些基本属性,和节点之间的层次关系,它相当于建立在 JavaScript 和 DOM 之间的一层“缓存”

<div class="hello">
    <span>hello world!</span>
</div>

上面的这段代码会转化可以转化为虚拟 DOM 结构

{
    tag: "div",
    props: {
        class: "hello"
    },
    children: [{
        tag: "span",
        props: {},
        children: ["hello world!"]
    }]
}

其中对于一个节点必备的三个属性 tag,props,children

  • tag 指定元素的标签类型,如“lidiv
  • props 指定元素身上的属性,如 classstyle,自定义属性
  • children 指定元素是否有子节点,参数以数组形式传入

而我们在 render 中编写的 JSX 代码就是一种虚拟 DOM 结构。

什么是 diff 算法?

其实刚开始学习 React 的时候,很多人可能都听说过 React 很高效,性能很好这类的话语,这其实就是得益于 diff 算法和 Virturl DOM 的完美结合。

单纯的我刚开始会认为

React 也只不过是引入了别人的 diff 算法而已,能有多厉害,又不是原创 ?

但当我查阅了众多资料后,发现被提及最多的是一个 “传统 diff 算法”

其实 React 针对 diff 算法做出的优化,才是我们应当学习的

React 将原先时间复杂度为 O( n 3 n^3 n3) 的传统算法,优化到了 O(n)

大致执行过程图

那 React 是如何实现的呢?

三个策略

为了将复杂度降到 O(n),React 基于这三个策略进行了算法优化

  1. Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计。
  2. 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
  3. 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。

针对这三个策略,React 分别对 tree diffcomponent diff 以及 element diff 进行算法优化

tree diff 分层求异

首先会将新旧两个 DOM 树,进行比较,这个比较指的是分层比较。又由于 DOM 节点跨层级的移动操作很少,忽略不计。React 通过 updataDepth 对 虚拟 DOM 树进行层级控制,只会对同层节点进行比较,也就是图中只会对相同颜色方框内的 DOM 节点进行比较。例如:

当对比发现节点消失时,则该节点及其子节点都会被完全删除,不会进行更深层次的比较,这样只需要对树进行一次遍历,便能完成整颗 DOM 树的比较

这里还有一个值得关注的地方:DOM 节点跨层级移动

为什么会提出这样的问题呢,在上面的删除原则中,我们发现当节点不存在了就会删除,那我只是给它换位了,它也会删除整个节点及其子节点吗?

如图,我们需要实现这样的移动,你可能会以为它会直接这样移动

但是实际情况,并不是这样的。由于 React 只会简单的进行同层级节点位置变化,对于不同层级的节点,只有创建和删除操作,当发现 B 节点消失时,就会销毁 B,当发现 C 节点上多了 B 节点,就会创建 B 以及它的子节点。

因此这样会非常的复杂,所以 React 官方并不建议我们进行 DOM 节点跨级操作

component diff

在组件层面上,也进行了优化

  • 如果是同一类型的组件,则按照原策略继续比较 虚拟 DOM tree
  • 如果不是,则将这个组件记为 dirty component ,从而替换整个组件下的所有子节点

同时对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点就可以节省大量的 diff 运算的时间,因此 React 允许用户通过 shouldComponentUpdate() 判断该组件是否需要进行 diff 算法分析

总的来说,如果两个组件结构相似,但被认定为了不同类型的组件,则不会比较二者的结构,而是直接删除

element diff

element diff 是专门针对同一层级的所有节点的策略。当节点在同一层级时,diff 提供了 3个节点操作方法:插入,移动,删除

当我们要完成如图所示操作转化时,会有很大的困难,因为在新老节点比较的过程中,发现每个节点都要删除再重新创建,但是这只是重新排序了而已,对性能极大的不友好。因此 React 中提出了优化策略:

允许添加唯一值 key 来区分节点

引入 key 的优化策略,让性能上有了翻天覆地的变化

那 key 有什么作用呢?

当同一层级的节点添加了 key 属性后,当位置发生变化时。react diff 进行新旧节点比较,如果发现有相同的 key 就会进行移动操作,而不会删除再创建

那 key 具体是如何起作用的呢?

首先在 React 中只允许节点右移

因此对于上图中的转化,只会进行 A,C 的移动

则只需要对移动的节点进行更新渲染,不移动的则不需要更新渲染

为什么不能用 index 作为 key 值呢?

index 作为 key ,如果我们删除了一个节点,那么数组的后一项可能会前移,这个时候移动的节点和删除的节点就是相同的 key ,在react中,如果 key 相同,就会视为相同的组件,但这两个组件是不同的,这样就会出现很麻烦的事情,例如:序号和文本不对应等问题

所以一定要保证 key 的唯一性

建议

React 已经帮我们做了很多了,剩下的需要我们多加注意,才能有更好的性能

基于三个策略我们需要注意

tree diff 建议:开发组件时,需要注意保持 DOM 结构稳定

component diff 建议:使用 shouldComponentUpdate() 来减少不要的更新

element diff 建议:减少最后一个节点移动到头部的操作,这样前面的节点都需要移动

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • React的diffing算法学习

    这段时间主要在学习React的使用,React是一个用于构建用户界面的框架,其使用了一些方式来使得视图渲染更加高效,这里主要记录一下学习期间了解到的Diffin...

    IMWeb前端团队
  • virtual DOM和diff算法(一)

    哈喽,大家好,今天是周一。周末回老家了,每次回老家后的第一个工作日都感觉很陌生,各位宝宝(づ。◕‿‿◕。)づ,有多久没回老家了?不管在哪里,记得好好照顾自己,好...

    用户3258338
  • React---虚拟DOM与DOM Diffing算法

    1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

    半指温柔乐
  • React入门学习

    React 是一个起源于 Facebook 的内部项目,因为当时 Facebook 对于市场上所有的 JavaScript MVC 框架都不太满意,所以索性就自...

    我没有三颗心脏
  • React 入门学习(十四)-- redux 基本使用

    在了解了 Antd 组件库之后,我们现在开始学习了 Redux ,在我们之前写的案例当中,例如:todolist 案例,GitHub 搜索案例当中,我们对于状态...

    小丞同学
  • 2020-5-21-理解React的渲染更新

    在聊渲染更新之前,我们不能忽视的一个概念是——React是JavaScript代码。

    黄腾霄
  • Python3入门机器学习(四)- kNN算法的学习与使用

    先基于原有的肿瘤病人的发现时间和肿瘤大小(特征)对应的良性/恶性(值)建立了一张散点图,横坐标是肿瘤大小,纵坐标是发现时间,红色代表良性,蓝色代表恶性,现在要预...

    Meet相识
  • React 入门学习(十)-- React 路由

    在我们之前写的页面当中,用我们的惯用思维去思考的话,可能会需要写很多的页面,例如做一个 tab 栏,我们可能会想每个选项都要对应一个 HTML 文件,这样会很麻...

    小丞同学
  • React入门学习笔记

    这里的constructor()初始化了props,state.value=null;当点击后,state.value=X;

    Mirror王宇阳
  • React List Diff

    对treeA做以上3步,就能得到treeA',树的规模较小时,一眼就能看出来,回想下是怎么做到的:

    ayqy贾杰
  • 机器学习算法入门

    问题导读 1.什么是程序? 2.什么是算法? 3.什么是机器学习算法? 4.机器学习的主要任务是什么? 5.机器学习+数据库=? 6.什么是自然语言处理? ...

    用户1410343
  • React 入门学习(十七)-- React 扩展

    学到这里 React 已经学的差不多了,接下来就学习一些 React 扩展内容,可以帮助我们更好的开发和理解,这部分的知识还有很多的东西可以探寻,比如:网红 R...

    小丞同学
  • JAVA入门学习四

    描述:有个这个关键字就是让有包的类对调用者可见,不用写全类名了; 比如我们前面在进行Scanner类示例化的对象来接收用户输入;

    WeiyiGeek
  • react-native学习之入门app

    1、项目初始化: react-native init MyProject 2、启动项目: cd MyProject react-native start 新开c...

    用户1141560
  • Python3入门学习四.md

    描述:模块就是更高级的封装,而模块就是程序;实际就是将一个个python文件编写的函数导入到其他的py文件中进行调用;

    WeiyiGeek
  • React 入门学习(十六)-- 数据共享

    在写完了基本的 Redux 案例之后,我们可以尝试一些更实战性的操作,比如我们可以试试多组件间的状态传递,相互之间的交互

    小丞同学
  • React 入门学习(十二)-- React 路由跳转

    默认情况下,开启的是 push 模式,也就是说,每次点击跳转,都会向栈中压入一个新的地址,在点击返回时,可以返回到上一个打开的地址,

    小丞同学
  • React 入门学习(十一)-- React 路由传参

    在上一篇中,我们学习了 React 中使用路由技术,以及如何使用 MyNavLink 去优化使用路由时的代码冗余的情况。

    小丞同学
  • 从0实现React 系列(二):组件更新

    假设React是你日常开发的框架,在日复一日的开发中,你萌生了学习React源码的念头,在网上一顿搜索后,你发现这些教程可以分为2类:

    一只图雀

扫码关注云+社区

领取腾讯云代金券