React 中 Virtual DOM 与 Diffing 算法的关系

时间: 2019-07-27阅读: 312标签: 算法

Virtual DOM 是一种编程理念

Virtual DOM 是一种编程理念。UI 信息被特定语言描述并保存到内存中,再通过特定的库,例如 reactDOM 与真实的 DOM 同步信息。这一过程成为 协调 (Reconciliation)。


与之对应的数据结构

Virtual DOM 反映到实际的数据结构上,就是每一个 react 的 fiber node

// UI 组件描述
const Span = (props) => <span></span>

// 实际的 Fiber node structure
{
  stateNode: new htmlSpanElement,
  type: "span",
  alternate: null,
  key: null,
  updateQueue: null,
  memoizedState: null,
  pendingProps: {},
  memoizedProps: {},
  tag: 1,
  effectTag: 0,
  nextEffect: null
}

这一抽离结构有点像 react 版本的 AST 抽象语法树。


Diffing 算法

问题

在 Virtual DOM -> Real DOM 之间的转换过程中,需要高效率的算法来支撑。由于某个时刻调用 react render() 方法生成的 react 元素组成的树,与下一次 state 或 props 变化时调用同一个 render 返回的树是不一样的,react 需要根据这两个不同的树来决定如何高效地让最新的 Virtual DOM 反应到真实 DOM 中。

解决方式

Diffing 算法就是解决如何更有效率地更新 UI 的关键。

react 采取了一个复杂度为 O(n) 的比较策略,这个策略有两个假设

  1. 两个不同类型的元素会产出不同的树
  2. 开发者可以通过 key prop 来保持元素的稳定


Diffing 策略

1. 对比根节点的元素

如果为不同类型,react 将会把原有的树拆卸并重新建立新的树。例如 <div> -> <span>。

  1. 当这颗树被拆卸后,对应的 DOM 节点也被销毁,组件实例回调用 willUnmount 方法。
  2. 当建立新的树的时候,对应的 DOM 将被插入到 DOM 中,并调用 didMount 方法。

在根节点以下的组件也会被卸载,它们的状态会被销毁。例如:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>


2. 对比同一类型的元素

当对比两个同类型的 react 元素时,react 会保留 DOM 节点,仅对比以及更新有变化的属性

<div className="before" title="stuff" />

<div className="after" title="stuff" />

通过对比两个元素,React 得知 className 变化,所以只需要更新 DOM 对应元素上的 class。

当处理完当前节点时,React 将会对子节点进行递归。


3. 对比同类型的组件元素

当一个 React 组件需要更新时(例如 props 有变化),组件实例保持不变,实例中的 state 能在不同渲染时保持一致。React 将更新该组件实例的 props 以保持与最新的元素的一致。并调用 该实例的原型 上的函数 getDerivedStateFromProps(官方文档是 componentWillReceiveProps 和 componentWillUpdate,但这将会被弃用)。

下一步是调用该实例的 render 方法,diffing 算法将在之前的结果和最新的结果中进行递归。


4. 对子节点进行递归

问题

在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表,当发现两个子元素有差异时,将生成一个「变种(mutation)」。

例如在子元素列表末尾新增元素时,更变开销比较小。比如:

// before
<ul>
  <li>first</li>
  <li>second</li>
</ul>

// after
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

React 会匹配两个 <li>first</li> 对应的树、两个 <li>second</li> 对应的树,然后插入 <li>third</li> 树。

但如果就这样简单实现的话,那么在列表头部插入会很影响性能,更变的开销会比较大。比如:

<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

React 会认为每个子元素都「改变(mutate)」了,而不会认为可以保持 <li>Duke</li> 和 <li>Villanova</li> 子树不变,从而导致重新渲染。这种情况下的低效可能会带来性能问题。

解决策略 Keys

为了解决以上问题,React 支持 key 属性。当子传入 key 到子元素时,React 通过 key 来匹配比较原有树上的子元素以及最新树上的子元素的差异。以下例子在新增 key 之后使得之前的低效转换变得高效:

<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

现在 React 知道只有带着 '2014' key 的元素是新元素,带着 '2015' 以及 '2016' key 的元素仅仅移动了位置。

所以一般在开发的时候最好使用一个有唯一属性的 id 来作为 key

<li key={item.id}>{item.name}</li>

在开发者自己确定数组数据不会轻易改变的情况下才可以用数组下表来作为 key。


权衡(Tradeoffs)

上述只是 协调算法(reconciliation algorithm)的实现细节而已。React 可以响应每一次 action 后重新渲染整个应用,最终结果也会是一样的。

需要明确知道的是,在当前上下文(this context)中重新渲染(rerender)意味着会调用所有的 component 的 render(),但并不意味着 React 会卸载(unmount)或重载(remount)它们。它(协调算法)只会用上述规则在其过程中找出不同。


站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/4979

JavaScript数据结构与算法-String

给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。主要就是用到了数组的 split 、 reverse 、 join 、 map 方法,原理:就是把字符串变成数组

用 JavaScript 学习算法复杂度

在后面的例子中,我将引用这两个数组,一个包含 5 个元素,另一个包含 50 个元素。我还会用到 JavaScript 中方便的 performance API 来衡量执行时间的差异

Js算法模式:动态规划和贪心算法

动态规划(Dynamic Programming,DP)是一种将复杂问题分解成更小的子问题来解决的优化算法。下面有一些用动态规划来解决实际问题的算法:给定一组硬币的面额,以及要找零的钱数,计算出符合找零钱数的最少硬币数量。

js之反转整数算法

将一个整数中的数字进行颠倒,当颠倒后的整数溢出时,返回 0 ;当尾数为0时候需要进行舍去。解法:转字符串 再转数组进行操作,看到有人用四则运算+遍历反转整数。

简单理解梯度下降算法及js实现

看了很多文章,梯度下降算法描述都比较艰涩难懂,比如说: 目标函数f(θ)关于参数θ的梯度将是损失函数(loss function)上升最快的方向。然后会推导出下面这个公式。

js字典树算法_Trie树(字典树)实现与应用

字典树又称Trie树、前缀字,单词查找树,是一种树形结构,是一种哈希树的变种,是一种用于快速检索的多叉树结构。 字典树是处理字符串常见的一种树形数据结构

一些常用的语音特征提取算法

语言是一种复杂的自然习得的人类运动能力。成人的特点是通过大约100块肌肉的协调运动,每秒发出14种不同的声音。说话人识别是指软件或硬件接收语音信号,识别语音信号中出现的说话人,然后识别说话人的能力

JS排序算法:记数排序

计数排序是一个非基于比较的[排序算法],该算法于1954年由 Harold H. Seward 提出。 它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围), 快于任何比较排序算法。

js多叉树结构的数据,parent表示法转成children表示法

要求是将这个数组转成一个children表示法的对象,即从根节点开始,每个节点存有其子节点数组。转化效果如下(节点必须有个唯一标识符,以下id就是,并且转化前后其他属性保持不变,这里为了显示简洁没有加入其他属性。

js二叉树的遍历算法

二叉树是非常重要的数据结构,其中一棵树最上面的点称为根节点,如果一个节点下面连接多个节点,那么该节点称为父节点,下面的节点称为子节点,二叉树的每一个节点最多有2个子节点,一个节点子节点的个数称为度,二叉树每个节点的度只能是0,1,2中的一个,度为0的节点称为叶节点。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!

文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全运营推广