虚拟DOM 和 Diff 算法
问题:我们知道state 更新组件的时候,只要state 变化就重新渲染视图,如果组件中只有一个DOM 元素需要更新时,也需要将整个组件内容重新渲染到页面中吗?
回答:不是,虚拟DOM 和 Diff 算法可以达到部分更新
虚拟DOM: 本质上是个 js 对象,用来描述页面UI (React 元素就是虚拟DOM)
执行过程
-
React组件配合 state 创建一个虚拟DOM树
-
根据虚拟DOM树,生成一个真正的 DOM 树,再渲染到页面中
-
当 state 或者 props 变化时,根据新的数据生成一个新的虚拟DOM树
-
将新旧虚拟 DOM 树进行对比,通过diff算法找到新旧虚拟DOM的差异点,最后将差异点更新到页面上
diff算法
1、diff算法的作用
计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面。
2、React 中的diff算法
调和是指 将Virtual DOM树转换成Actual DOM树的最少操作的过程,而 diff算法是调和的具体实现。
3、diff 策略
- 策略一
(tree diff)
: 按照树的层级进行比较叫做 tree diff,如果该节点不存在,则整个删除,不再继续比较
如果DOM节点出现了跨层级操作,diff会咋办呢?
diff只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。
如上图所示,以A为根节点的整棵树会被重新创建,而不是移动,官方也建议不要进行DOM节点跨层级操作,可以通过CSS隐藏、显示节点,而不是真正地移除、添加DOM节点。
- 策略二
(component diff)
: 每一层中组件的对比叫做 component diff
-
同一类型的两个组件,按层级比较Virtual DOM树。
-
同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,可以通过 shouldComponentUpdate() 来判断是否需要判断计算。
-
不同类型的组件,将一个(将被改变的)组件判断为dirtycomponent(脏组件),从而替换整个组件的所有节点。
-
策略三
(element diff)
:如果两个组件类型相同,则需要对比组件中的元素,叫做 element diff,
diff提供三种节点操作:删除、插入、移动。插入:组件 C 不在集合(A,B)中,需要插入
删除:
- 组件 D 在集合(A,B,D)中,但 D的节点已经更改,不能复用和更新,所以需要删除 旧的D ,再创建新的。
- 组件D之前在集合(A,B,D)中,但集合变成新的集合(A,B)了,D 就需要被删除。
移动:同一层级的同组子节点元素发生变化,对同一层级的同组子节点添加唯一key进行区分,从而移动。
虚拟DOM和Diff 算法代码演示
组件 render() 调用后,根据状态
和JSX 结构
生成虚拟DOM对象
import React from 'react'
import ReactDOM from 'react-dom'
class App extends React.Component {
state = {
number:0
}
handleClick=()=>{
this.setState(()=>{
return{
number:Math.floor(Math.random() * 3)
}
})
}
//render 方法调用并不意味着浏览器的重新渲染!!! (只会更新h1文本节点内容)
//render 方法调用仅仅说明要进行diff
render(){
const el = (
<div>
<h1>随机数:{this.state.number}</h1>
<button onClick={this.handleClick}>重新生成</button>
</div>
)
console.log(el);
return el
}
}
ReactDOM.render(<App/>, document.getElementById('root'))