React Diff算法

React最神奇的亮点就是虚拟DOM和高效的diff算法。

在React中,UI界面由组件构成。当前组件的状态发生变化时,真实DOM树需要重新更新渲染,而真实DOM树来源于React的虚拟DOM树。React将虚拟DOM树转换为真实DOM树的最小计算过程称为调和(reconciliation),而diff算法便是调和的具体实现!

  • setState()方法会更改组件状态,真实DOM树需要重新更新渲染
  • 调用setState()方法,React不是立即对其更新,而是使用了事件轮询对变更做批量处理绘制。(多个状态改变合并到一次页面更新中)
  • 真实DOM和虚拟DOM是通过ReactDOM转换器适配转换

React diff 算法

简单来说,diff算法就是给定任意的两棵树从中找到最少的转换步骤,或者说是从上一个渲染转到下一个渲染的最少步骤。

diff 策略

diff 算法并非React 推出的,我们管它叫传统diff算法,React中的diff算法则是对传统diff算法的做了极大性能提升。

diff算法作用

计算出虚拟DOM中真正变化的部分,并只针对该部分进行真实DOM操作,而非重新渲染整个页面。不是重新丢一份源代码给浏览器重新加载渲染到页面,而是告诉浏览器你只要修改哪一部分即可。

传统diff算法

传统diff算法是将新旧两棵树每个节点逐一对比,循环遍历所有子节点,然后判断子节点的更新状态(又一次遍历),其复杂度为O(n^3)(n是树中节点的总数),效率很低。有多低呢?打个比方,假如有1 000个元素,那么需要计算10亿次左右。将此算法应用到计算机用于前端渲染,那么代价太大了。而React将时间复杂度为O(n^3)的算法直接转为O(n),具体怎么实现的呢?

React diff策略

diff 的核心:对比和修改。 React基于这两点实现了一个启发式的O(n)的算法:

  • 两个不同类型的元素将产生两个不同的树;
  • 同一级的一组子节点,可以从中埋入一个key属性用于区分;

在此基础上React大胆采用了3种策略:

  • DOM节点跨层级操作特别少,所以可以忽略;
  • 拥有相同类的两个组件会生成相似树形结构,拥有不同类的两个组件将会产生不同树形结构;
  • 一层级一组子节点通过唯一id(key)区分。

下面来具体介绍这3种策略的具体做法。

Tree Diff

Tree Diff是两棵新旧虚拟DOM树按照层级的对应关系,把同一层级的节点遍历一遍,即同层比较,这样就能快速找到有差异的地方。

同层求异

它首先会比较最顶层的虚拟DOM节点是否一致,如果一致的话,就继续比较下一层的节点;如果不一致的话,react就会把这个节点及其下面所有节点全部删掉,重新生成一遍新的DOM,然后用新的DOM替换原始页面的DOM。也就是只需要遍历一遍。

在这里插入图片描述
Tree的比较适用于界面DOM节点跨层级操作少的情形,这样就可以忽略不计层级带来的影响。

DOM跨层级操作

如果DOM节点出现了跨层级操作,diff 会咋办呢?

答:diff只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。
在这里插入图片描述

如上图所示,以A为根节点的整棵树会被重新创建,而不是移动,因此 官方建议不要进行DOM节点跨层级操作,可以通过CSS隐藏、显示节点,而不是真正地移除、添加DOM节点。

Component Diff

React对组件的比较,有两种策略:

  • 对于类型相同的组件,根据Virtual DOM树按照原来的策略(Tree->Component->Element )继续比较Virtual DOM Tree即可。
  • 对于类型不同的组件,React会将这个组件内部所有的子节点重新替换。

在这里插入图片描述
组件B变为组件C,虽然子组件内容相似,但一旦识别到父组件B和C不同,就直接删除B组件,重新创建C组件,B组件在React中称为dirty component(可以理解为组件最近一次发生改变,但还未重新渲染的组件)

Element Diff

React在遇到类型相同的组件时(衔接Component Diff类型相同情况),会继续对组件内部元素进行对比,检查内部元素异同,这就是Element Diff。(节点处于同一层级时)它可以进行插入、移动、删除这3种操作。

  • 插入:元素 C 不在集合(A,B)中,需要插入
  • 删除:
    1. 元素 D 在集合(A,B,D)中,但 D的节点已经被完全更改,不能复用和更新,所以需要删除旧的 D ,再创建新的。
    2. 元素 D 之前在 集合(A,B,D)中,但集合变成新的集合(A,B)了,D 就需要被删除。
  • 移动:元素 D已经在集合(A,B,C,D)里了,且集合更新时,D没有发生更新,只是位置改变,如新集合(A,D,B,C),D在第二个,无须像传统diff,让旧集合的第二个B和新集合的第二个D 比较,并且删除第二个位置的B,再在第二个位置插入D,而是 (对同一层级的同组子节点) 添加唯一key进行区分,移动即可。
传统 diff 移动同级元素

在这里插入图片描述

旧元素A、B、C、D发生变化需要排列为B、A、D、C,当发现B不等于A时,则将B节点创建并插入至新组建,同时删除A节点,以此类推。即使有相同的节点,而且仅仅只是移动了位置,但还是需要删除并重写,无疑这种操作很繁琐低效。

React diff 移动同级元素

在React中,它可以给每个同层节点设置一个唯一的key。当元素A、B、C、D发生变化需要排列为B、A、D、C时,diff差异化对比后发现新旧节点存在相同的,则无须进行重新创建和删除,只需将旧的节点集合进行位置移动即可。

其内部具体执行是,先将新的节点集合进行遍历循环,然后通过唯一标记key去老的节点集合中寻找是否有命中的标记,如果有,就执行移动操作。与此同时需要注意的是,(执行移动操作的节点)当前节点在旧集合中索引的值oldIndex必须小于新集合中当前节点的索引值newIndex,因为这样能节省更多不必要的操作从而节省时间,更加优化算法效率。
在这里插入图片描述

总结

在这里插入图片描述

  • Tree Diff:采用分层求异的策略,将新旧两棵DOM树按照层级对应的关系进行对比,这样只需要对树进行一次遍历,就能够找到哪些元素是需要更新的。
  • Component Diff:查看两个组件的类型是否相同。如果类型不同,则需要更新,更新时先把旧的组件删除,再创建一个新的组件插入之前删除的位置。类型相同时,暂时不需要更新。
  • Element Diff:通过设置唯一key值,对元素diff进行优化。(组件类型相同时看内部元素)元素发生了改变,则找到需要修改的元素,有针对性进行修改。

=============================================================================================

习惯边学习边笔记以加深记忆,本文主要参阅《React+Redux前端开发实战》一书相关章节,加以自己理解作补充而行文。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值