React虚拟DOM和Diff算法和Key的作用

一、React虚拟DOM的本质就是一个JS对象,虚拟DOM减少了真实DOM的操作,当修改数据的时候,就是修改虚拟DOM产生全新的虚拟DOM,新旧虚拟DOM使用diff算法,得到patch(也就是需要修改的部分),然后将这个patch打到浏览器的DOM上(减少重绘和回流,从而达到性能优化的目的)

二、diff算法:把树形结构按照层级分解,只比较同级元素。同层比对,列表使用不同的key值,对比差异,选择性子树渲染。

三、Key的作用
这个要结合diff算法一起看看额。。。
Diffing 算法,当对比两颗树时,React 首先比较两棵树的根节点。不同类型的根节点元素会有不同的形态。
3.1比对不同类型的元素
当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。举个例子,当一个元素从 < a > 变成 < img >,从 < Article> 变成 < Comment>,或从 < Button> 变成 < div> 都会触发一个完整的重建流程。
当拆卸一棵树时,对应的 DOM 节点也会被销毁。组件实例将执行 componentWillUnmount() 方法。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法。所有跟之前的树所关联的 state 也会被销毁。
在根节点以下的组件也会被卸载,它们的状态会被销毁。比如,当比对以下更变时:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

React 会销毁 Counter 组件并且重新装载一个新的组件。

3.2比对不同类型的元素
当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树。举个例子,当一个元素从 < a> 变成 < img>,从 < Article> 变成 < Comment>,或从 < Button> 变成 < div> 都会触发一个完整的重建流程。

当拆卸一棵树时,对应的 DOM 节点也会被销毁。组件实例将执行 componentWillUnmount() 方法。当建立一棵新的树时,对应的 DOM 节点会被创建以及插入到 DOM 中。组件实例将执行 componentWillMount() 方法,紧接着 componentDidMount() 方法。所有跟之前的树所关联的 state 也会被销毁。

在根节点以下的组件也会被卸载,它们的状态会被销毁。比如,当比对以下更变时:

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

React 会销毁 Counter 组件并且重新装载一个新的组件。

3.3比对同一类型的元素
当比对两个相同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有改变的属性。比如:

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

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

通过比对这两个元素,React 知道只需要修改 DOM 元素上的 className 属性。

当更新 style 属性时,React 仅更新有所更变的属性。比如:

<div style={{color: 'red', fontWeight: 'bold'}} />

<div style={{color: 'green', fontWeight: 'bold'}} />

通过比对这两个元素,React 知道只需要修改 DOM 元素上的 color 样式,无需修改 fontWeight。

在处理完当前节点之后,React 继续对子节点进行递归。

3.4对子节点进行递归
在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个 mutation。在子元素列表末尾新增元素时,更变开销比较小。比如:

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

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

React 会先匹配两个

  • first
  • 对应的树,然后匹配第二个元素
  • second
  • 对应的树,最后插入第三个元素的
  • third
  • 树。

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

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

    React 会针对每个子元素 mutate 而不是保持相同的 Duke和Villanova子树完成。这种情况下的低效可能会带来性能问题。

    重点来了~~~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 的元素仅仅移动了。

    现实场景中,产生一个 key 并不困难。你要展现的元素可能已经有了一个唯一 ID,于是 key 可以直接从你的数据中提取:

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

    当以上情况不成立时,你可以新增一个 ID 字段到你的模型中,或者利用一部分内容作为哈希值来生成一个 key。这个 key 不需要全局唯一,但在列表中需要保持唯一。

    最后,你也可以使用元素在数组中的下标作为 key。这个策略在元素不进行重新排序时比较合适,但一旦有顺序修改,diff 就会变得慢。

    当基于下标的组件进行重新排序时,组件 state 可能会遇到一些问题。由于组件实例是基于它们的 key 来决定是否更新以及复用,如果 key 是一个下标,那么修改顺序时会修改当前的 key,导致非受控组件的 state(比如输入框)可能相互篡改导致无法预期的变动。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值