React将DOM抽象为虚拟DOM, 然后通过新旧虚拟DOM 这两个对象的差异(Diff算法),最终只把变化的部分重新渲染,提高渲染效率的过程; (概念讲完再描述一遍)
一句话: 用 JS 对象的形式,来表现一棵真是的 DOM 树;
Diff 算法
- 当你实际开发使用React的时候,在某个时间点 render() 函数创建了一棵React元素树,也就模拟一个虚拟 DOM 树,
- 在下一个state或者props更新的时候,render() 函数将创建一棵新的React元素树, 也就模拟了一个新的虚拟 DOM 树,
- 既然模拟出了新旧两棵DOM 树, 那么如何高效的进行新旧两棵树的对比呢??
- 当然是使用 DIff 算法...
-
传统的 Diff 算法也是一直都有的;
- 但是它的时间复杂度为O(n^3)
意思是: 在React中更新10个元素则需要进行1000次的比较。(1000个===10亿)
- 但是它的时间复杂度为O(n^3)
-
React 通过制定大胆的策略,将 O(n^3) 复杂度的问题转换成 O(n^1=n) 复杂度的问题。
- 两个不同类型的元素会产生不同的树 <MyTest /> 和 <MyComponent />
- 对于同一层级的一组子节点,它们可以通过唯一 key 进行区分
- 基于以上两个前提策略,React 分别对 tree diff、component diff 以及 element diff 三种 diff 方法是 进行算法优化,
- 下面介绍优化后的几种算法
Tree Diff
概念: 将新旧两颗虚拟 DOM 树,按照层级对应的关系,从头到尾的遍历一遍,,就能找到那些元素是需要更新的,这种方式: Tree Diff
1 只会对相同颜色方框内(同级)的DOM节点进行比较,即同一父节点下的所有子节点
2 当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较
- 执行过程:create A -> create B -> create C -> delete A
Component Diff
不同组件之间的对比
概念: 在对比每一个层级的时候,会有自己的组件,这种组件的对比方式就叫: Component Diff ;
这种对比方法其实比较的就是类型.↓↓↓
- 如果类型相同,暂时不更新,
- 如果类型不相同,就需要更新; ( 删除旧的组件,再创建一个新的组件,插入到删除组件的那个位置)
- 执行过程:delete D -> create G
Element Diff
同一层级中元素之间的对比
概念: 在类型相同的组件内, 再继续对比组件内部的元素,查看内部元素是否相同,如果需要修改,找到需要修改的元素,进行针对性的修改! 这种方式就叫: Element Diff
三种节点操作:
1 INSERT_MARKUP(插入)
2 MOVE_EXISTING(移动)
3 REMOVE_NODE(删除)
INSERT_MARKUP:新的 component 类型不在老集合里,需要对新节点执行插入操作。
MOVE_EXISTING:老的集合包含新的 component 类型,就需要做移动操作,可以复用以前的 DOM 节点。
REMOVE_NODE:老的 component 不在新集合里的,需要执行删除操作 或者 老的 component 类型在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作
-
执行过程:B != A,则创建并插入 B,删除 A;以此类推,创建并插入 A、D、C,删除 B、C、D
执行过程:B、D 不做任何操作,A、C 进行移动操作
diff 算法总结:
保持完整的结构,有利于性能的提升
尽量使用相同类型的组件
在进行 Element 或Component 级别对比的时候,为了提高对比的效率, React 推荐我们为每个 for 循环创建出来的元素或者组件,提供一个唯一的 key;
Tree diff :将新旧两棵 DOM 树,按照层级对应的关系,这样只需要对树进行一次遍历,就能够找到哪些元素是需要更新的;
Component Diff: 在对比每一层的时候,每一层都有自己的组件, 那么组件之间的对比,叫做 Component diff , 对比的方式,就是查看两个组件的类型是否相同,如果相同,则暂时认为不需要更新,如果类型不同,则表示更新(先把旧的组件删除,再创建一个新的组件,插入到刚才删除的位置);
Element Diff:如果新旧 DOM 树中的组件类型相同,会继续比较这两个组件内部的元素是否也相同,如果元素发生了改变,则找到需要修改的元素,有针对性的修改,这种组件内部元素级别的对比叫: Element Diff;