虚拟DOM及Diff算法

167 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情

一.虚拟DOM

虚拟DOM对象描述:

就是一个普通的JS对象,用来描述我们希望在页面中看到的HTML结构内容

例如:

<div>123</div>

2.原生DOM

也是一个JS对象,是浏览器默认提供的,DOM对象和HTML元素之间是一一对应的关系。虚拟DOM属性少,原生DOM属性多

可在控制台打印查看

也可

for(var i in DOM){console.log(i)}

3.为什么要使用虚拟Dom

直接使用真实DOM

状态1 + jsx ===> 真实DOM1

状态2 + jsx ===> 真实DOM2

第一次生成时,速度很快,第二次生成时,直接替代

引入虚拟DOM

状态1 + jsx ===> 虚拟DOM1 ===> 真实DOM1

状态2 + jsx ===> 虚拟DOM2 ===> 对比虚拟DOM12的不同处 ===> 部分更新真实DOM1,得到真实DOM2

4. 虚拟DOM优点

  1. 真正的DOM对象属性很多,处理起来不方便

  2. 性能角度

  3. 真正价值: 跨平台

虚拟DOM --- reactDOM ---> Web DOM

虚拟DOM --- react-native ---> APP

虚拟DOM --- react-360 ---> VR

二.Diff算法

1.整体说明:

  • 第一次页面渲染的过程: JSX + state = 虚拟DOM树(JS对象) + 浏览器中看到的HTML结构内容

  • 当更新了状态,就会重新渲染组件,也就重新生成一颗新的虚拟DOM

  • Diff算法的使用:对比初始虚拟DOM树和更新后的虚拟DOM树,找到不同之处,最终,只将不同的地方更新到页面中

2.ReactDiff

官方链接思密达

复杂度

由于Diff操作本身也会带来性能损耗,React文档中提到,即使在最前沿的算法中,将前后两棵树完全对比的算法的复杂程度为O(n^3),其中n是树种元素的数量。

如果React中使用了该算法,那么展示1000个元素所需要执行的计算量将在十亿的量级范围。这个开销实在是太大。

为了降低算法复杂度,ReactDiff会预设三个限制:

1.只对同级元素进行Diff。如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。

2.两个两个不同类型的元素产生出不同的树。如果元素由div变成pReact会销毁div及其子孙节点,并新建p及其子孙节点

3.开发者可以通过key prop来暗示哪些子元素在不同的渲染下能保持稳定

image.png

3.一个组件内部更新机制

想让状态发生变化,就调用setState(),只要调用setState(),就会执行组件的render方法。来重新渲染组件

注意:render重新执行,不代表把整个组件重新渲染到页面中。而实际上,React内部会使用虚拟DOMDiff算法来做到部分更新:部分更新(打补丁),只将变化的地方重新渲染到页面上,这样,及量减少了DOM操作

4.Diff算法情况

Diff算法情况1

如果两颗树的根元素类型不同,React会销毁旧树,创建新树

<div>               <span>
  <Counter />  ===>  <Counter />
</div>              </span>

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

Diff算法情况2

对于类型相同的React DOM 元素,React会对比两者的属性是否相同,只更新不同的属性

当处理完这个DOM节点,React就会递归处理子节点

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

     ===>

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

React只修改DOM元素上的 className 属性

Diff算法情况3

当一个组件更新时,组件实例会保持不变,因此可以在不同的渲染时保持state一致。React 将更新该组件实例的 props 以保证与最新的元素保持一致,并且调用该实例的相应方法,然后调用 render() 方法,Diff 算法将在之前的结果以及新的结果中进行递归。

Diff算法情况4

当在子节点的后面添加一个节点,这时候两棵树的转化工作执行效率非常高

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

React 会先匹配前两个元素最后插入第三个元素到树上

Diff算法情况5

将新增元素插入到表头,更新开销会比较大

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

React并不会意识到应该保留 <li>Duke</li> 和 <li>Villanova</li>,而是会重建每一个子元素。这种情况会带来性能问题

5.key

为了解决上述问题,React 引入了 key 属性。当子元素拥有key时,React使用key来匹配原有树上的子元素以及最新树上的子元素。

<ul>                                    <ul>
  <li key="2015">Duke</li>                <li key="2014">Connecticut</li>
  <li key="2016">Villanova</li>   =====>  <li key="2015">Duke</li>
</ul>                                     <li key="2016">Villanova</li>
                                        </ul>
  •  说明:key属性在React内部使用,但不会传递给你的组件
    
  •  推荐:在遍历数据时,推荐在组件中使用key属性如:`<li key={item.id}>{item.name}</li>`
    
  •  注意:1.key只需要保持与他的兄弟节点唯一即可,不需要全局唯一
    
  •          2.尽可能的减少数组index作为key,数组中插入元素等操作,会使得效率低下
    

key异常情况

描述

组件中key变化,会导致组件销毁重建

场景:

如使用Math.random()作为子组件的key的话会导致父组件重新渲染时子组件即使其他数据都未发生变化也会导致其重建

应用:

可应用于单页面的不同应用场景,如点击编辑按钮会渲染数据,再点击添加清空数据

如有与官网不符处,一切以官网为准,如有错误请提出,感觉我写的不好请谅解