执行时间: render生成element树形数据之后,渲染dom element之前。
优势: 算法复杂度为 O(n),传统diff算法复杂度为O(n^3)。
基于假设:
- 两个不同类型的element会产出不同的树;
- 开发者可以通过设置
key
属性,来告诉react,哪些子元素可以不变。
对比形式:
1.不同类型的element
什么是不同类型的element?比如,div → a; img → span; 等等。
例如div→ a,这里会把原来的节点div和其子节点卸掉,并渲染新的节点a。
对于div的示例,会执行componentWillUnmount迎来生命周期的结束。对于组件a,则会在实例化之后,执行UNSAFE_componentWillMount 和 componentDidMount,完成渲染。
2.相同类型的dom element
例如,div className='a' → div className='b; div style={{width: 20px, height 30px}} → div style = {{width: 20px, height: 40px}};
dom节点保持一样,只做属性的对比。
对于div className='a' → div className='b这种情况,react只更新div dom的class。
对于div style={{width: 20px, height 30px}} → div style = {{width: 20px, height: 40px}};这种情况,react只会更改style里的height。
3.相同类型的component element
什么是相同类型的component element?比如,有个组件叫Cmp, 它要更新了。
这种组件在更新的时候,实例是不变的,react将通过修改组件实例的props,以保element和新的element是一致的,并调用实例的UNSAFE_componentWillReceiveProps,UNSAFE_componentWillUpdate,componentDidUpdate方法。
对比过程:
对子节点递归
react只对相同层级的子节点递归对比。而且大部分会穿插以上三种对比形式。比如最常见的列表。
<ul className='ul0'>
<li>first</li>
<li>second</li>
</ul>
<ul className='ul1'>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
如果单纯的从ul0变化到ul1,react在处理element的时候,对于li的元素,会直接走不同类型的element。 对比之后,直接将third 插入。
对于ul来讲,会走相同类型的dom element。 对比之后,修改ul的className属性
如果你的li是一个自定义的Component,那就会涉及到相同类型的component element。
以上是添加元素的例子,似乎并没有什么额外的开销,如果想在first之前插入 zero,试想react会做什么?
对,react会把0,1,2号li全部重新创建一遍,其中还涉及到原来1,2元素的卸载。
所以,有了Key。
key
Key的出现,让开发者可以给element一个特殊的标识,如果key不变,说明这个element,还是之前的element,只是移动了顺序。如果变了,那就新建。
所以,如果在react中用数组的index作为key,需要保证,数组不会重新排序。如果重新排序了,状态就会拿之前的,导致排序失效。这也是为什么,一个数组map不传key,react会给warning的原因。
可以感受一下: