虚拟DOM是和真实DOM相对应的,是对结构化文本的抽象表达,即对HTML文本的抽象表达,
虚拟DOM是建立在真实DOM上,对真实DOM的抽象,以解决DOM操作效率低下的问题
使用普通的js对象来描述元素
React的调和过程:每次组件的状态和属性更新,组件的render方法都会返回一个新的虚拟DOM对象,用来描述新的UI结构,React会通过比较两次虚拟DOM结构的变化找出差异部分,更新到真实DOM上,从而减少最终要在真实DOM上的操作,提高执行过程,这一过程就是React的调和过程
调和过程的关键是比较两个属性结构的Diff算法
注意:Diff算法比较的是新旧虚拟DOM,只是更新结果更新到真实DOM上
正常情况下,树形结构差异的算法的时间复杂度是O(N^3),React通过总结DOM操作的实际场景,提出了两个在绝大多数情况下都成立的假设,基于这两个假设,实现了在O(N)时间复杂内完成的两颗虚拟DOM树的比较
1)如果两个元素的类型不同,那么生成两颗不同的树
2)为列表的元素设置key属性,用key标识对应元素的在多次render过程中是否发生变化
React比较两颗树是从树的根节点开始比较,根节点的类型不同,执行的操作也不同
组件A变为组件B,标签C变为标签D,组件A变为标签C
如果根节点类型发生变化,React会认为新的树和旧的树完全不同,不会继续比较其他属性和子节点,而是销毁就DOM,创建新DOM(包括虚拟DOM和真实DOM)
DOM节点类型分为DOM元素类型和React组件类型
在旧的虚拟DOM被拆除的过程中,旧的元素类型的节点会被销毁,旧的组件实例的componentWillUnmount会被调用
在重建的过程中,新的DOM元素会被插入DOM树中,新的组件实例的componentWillMount和componentDidMount方法会被调用,重建的虚拟DOM树会被整体更新到真实DOM树中。这种操作需要大量DOM操作,更新效率最低
会保留根节点,比较根节点的属性,然后只更新那些变化了的属性(包括子节点)
组件实例不会被销毁,而是执行更新操作,同步变化的属性到虚拟DOM上,这一过程组件实例的componentWillReceiveProps()componentWillUpdate()会被调用。
对于组件节点,React是无法直接知道如何更新真实DOM树,需要在组件更新并且render方法执行完成后,根据render返回的虚拟DOM树结构决定如何更新真实DOM
比较完根节点之后,会以同样的原则继续递归比较子节点,每个子节点相对于其层级以下的节点来说又是一个根节点。如此递归比较,直到比较完两棵树上所有节点,计算得到最终的差异,更新到DOM树中
默认情况下,React会按照顺序逐一比较两棵树上对应的节点
但是,如果子节点顺序被打乱,更新效率将会下降,因此React提供了key属性作为标记。
当一组子节点定义了key,React会根据key来匹配子节点,在每次渲染之后,只要子节点的key值没有变化,就认为是同一节点
尽量不要使用列表的索引值作为key,因为当列表中的元素顺序发生改变的话,可能会导致大量的key失效,进而引起大量的修改操作
使用shouldComponentUpdate
父组件的每次render都会触发子组件componentWillReceiveProps的调用,进而调用子组件的render方法,但是子组件的props可能并没有发生变化,改变的只是父组件的props和state,所以子组件的render是没有必要的,不仅多执行了一次render和多比较了一次虚拟DOM。
使用shouldComponentUpdate可以避免调用子组件的更新过程
使用浅比较(只比较对象的第一层属性),可以使用React的PureComponent来代替手写shouldComponentUpdate逻辑
class C extends React.PureComponent{}