前言
VirtualDOM和Diff算法 是react核心的原理。
原理实现: 用js中的狭义对象(obj)表示所有的DOM节点,所以VirtualDOM 是js中的一个狭义对象。在react中,大体上就是围绕实际对象和VirtualDOM这俩个狭义对象进行各种操作(可以看做是”类”里面的静态方法属性),比如,对比俩者之间的差异操作,根基实际对象,渲染实际DOM的操作。
Diff算法,其实就是一种基于假设的LCS的一种实现。
VirtualDOM
是一种算法思想,或者说是设计模式。在js中本质上就是一个js对象。在其他任何语言里都可以实现VirtualDOM。
Diff算法
一种算法。重点是react进行假设,我感觉其实就是“动态规划”的LCS嘛。
假设前提
根节点不同类型
只要根节点类型不同,React就会销毁旧节点树,创建新节点树。
在销毁节点树时,旧的DOM节点会被销毁,React组件实例触发componentWillUnmount()方法。
在创建新节点树时,新的DOM节点被插入DOM树,React组件实例依次触发componentWillMount()方法和componentDidMount()方法。
根节点类型不同,根节点下任何组件或元素节点都会被卸载,并且把其state销毁,比如如下两棵节点树,diff后,卸载旧的Home组件,挂载一个新的Home组件实例:
节点相同类型
当比较两个相同类型的React节点(包括组件和html元素)时(包括所有节点,根节点和所有子节点),React比较两者的属性,不改变DOM节点,只更新当前节点需要改变的属性,如:
对于如上两个元素,React仅会改变当前节点需要改变的className属性。
样式更新
如果节点需要更新样式属性,React仅会更新当前节点需要改变的样式属性,如:
React仅会更新当前节点的font-size属性值。
React重复以上步骤,递归比较处理两棵React节点树.
组件节点相同类型
前面的diff比较都是以html元素为例,但是对于组件,前文的理论依然适用,只是关于组件的比较,我们还需要了解更多内容:当某一组件更新了,组件实例还没有改变,其组件状态是通过渲染维护的。React更新当前组件实例的(props)属性以匹配新元素,并且调用当前实例的componentWillReceiveProps()方法和componentWillUpdate()方法;然后调用render()方法并且使用diff算法递归比较已经更新的节点树和新返回的节点树。
递归比较子节点
默认地,在递归比较DOM节点的子节点时,React同时遍历两个子节点列表,当发现不同时,立即做出改变。
比如,需要在一个节点的子节点后插入一个子节点,性能是很好的,比较如下两棵节点树:
1 | React匹配ul节点的子节点列表,依次比较<li>first</li>和<li>second</li>,直到发现当前元素缺少子节点<li>third</li>,就立即插入一个<li>third</li>节点。 |
但是如果不是在末尾插入新的子节点,而是在头部插入:
React比较第一个子节点时,就发现不同,立马改变该节点,随后全部子节点都需要改变,这样的性能表现很不好。
key属性
为了解决上面提到子节点比较的问题,React提供了key属性,如果某子节点拥有key属性,React会在原始子节点树里使用key值来比较该子节点,比如,对于如上实例,使用key属性:
对于如上这种结构,React会创建key值为3的节点,而只是移动key值为1和2的节点。
key属性值与唯一性
既然通过key值比较子节点,那么很明显,至少在子节点树里,每个key值必须是唯一的,但是并不需要保证key值是全局唯一的。
通常我们可以使用服务端返回的列表项数据中的id或index当key值。
diff算法的不足
我们必须明白,React diff算法决定如何重新渲染组件,但这只是一个细节实现,React可以对每一个action行为都重新渲染整个应用,最后的视图都是一样的,我们在不断地精炼探索式算法以提高应用性能。
目前的实现方式中,如前文的实例,我们知道在子节点间是可以不被重新渲染而只发生移动的,但是除此之外,子节点并不能被重用,算法将会重新渲染整个子节点树。
前文提到,React的diff算法是探索式的,基于两点假设,如果这两点假设不满足,性能就会降低:
算法不会比较不同组件类型的子节点
key值应该是稳定的,可预测的,唯一的。不稳定的key值会产生很多不必要的重绘和子组件状态的丢失。
什么是“React Fiber”?
fiber是React 16中新的和解引擎。它的主要目的是使虚拟DOM能够进行增量渲染。了解更多。
参考