作者:Dearlily2046_394 | 来源:互联网 | 2023-06-21 20:58
前提:在第一篇vue3 diff第一篇:diff算法代码解析我们进行了diff核心算法解析,会引发一些思考。
太长不看版:
1. 新增在同级节点非尾部位置新增或删除,都会导致新增位置以及后面的全部节点无法复用 (并不仅仅指v-for出来没key的)
2. vue3 相对于vue2 性能优化点除了lis(最长递增子序列)实现最小化移动以外,只diff动态节点是一个很大的优化点
(flutter里也有类似优化,const声明静态节点)
2021-6-18新增
这两天研究react发现在文档中有对思考一这种现象具体的场景描述,react协调
思考一、由于是同级比较,块状节点变成vdom后也有children(不管是不是v-for循环出来的),在vue3会进入patchUnkeyedChildren,那在页面新增或删除,会导致整个页面dom都会重建??
// 这是楼层板块
// 这是新闻板块
测试
测试
...
针对以上结构我们新增一个header板块
// 这是楼层板块
// 这是新闻板块
测试
测试
...
以上结构,如果说在末尾,也就是新闻板块下面新增footer板块,patch没问题,一一对应然后patchchildren,里面的child还能复用
但是,如果在顶部新增header板块,这就行不通了。我们再看patch代码
patchUnkeyedChildren方法简要代码
const patchChildren: PatchChildrenFn = (n1, n2,...) => {
const { patchFlag, shapeFlag } = n2
if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
patchKeyedChildren(){}
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
1. 遍历新旧中最短的节点,依次patch,如果不是相同节点,直接卸载
const commOnLength= Math.min(c1.length, c2.length)
for (let i = 0; i c2.length? unmountChildren(c1,...,commonLength) : mountChildren(c2,...,commonLength)
}
}
很明显 patch(c1[i],c2[i]) ,新节点header和旧节点floor比较,虽然能复用,但是子节点就完全不同了。
实际场景:v-if渲染,或者拖拽,删除
结论:新增在同级节点非尾部位置新增或删除,都会导致新增位置以及后面的全部节点无法复用,vue2的双端比较大体也是如此
所以:key的重要性就不必说了
并且尽量不要跨层级的修改dom
在开发组件时,保持稳定的 DOM 结构会有助于性能的提升
思考二、在页面上很多元素都是静态不变的,这种也会参与diff吗?
这是vue3相对vue2做的优化,使用patch flag 优化静态树,只diff会变化的数据
vue3版template 转为render函数在线查看点我,该地址在线将template转为render函数,再由下图中的_createVNode,_createBlock转为vdom
企业微信截图_16233921121799.png
vue2版template转为render函数在线查看点我
企业微信截图_16233915971830.png
从上面可以发现,vue3使用_createBlock创建了一个fragment包裹了动态节点,并且在末尾还根据节点动态值不同分为STABLE_FRAGMENT, TEXT。如果仅仅是动态属性,就只标记了属性PROPS。具体还有事件的缓存,可以在在线地址中点击options仔细查看区别
这里是源码createBlock部分,实际上也是调用了createVNode生成节点
export function createBlock(
type: VNodeTypes | ClassComponent,
props?: Record | null,
children?: any,
patchFlag?: number,
dynamicProps?: string[]
): VNode {
const vnode = createVNode(
type,
props,
children,
patchFlag,
dynamicProps,
true /* isBlock: prevent a block from tracking itself */
)
// save current block children on the block vnode
vnode.dynamicChildren =
isBlockTreeEnabled > 0 ? currentBlock || (EMPTY_ARR as any) : null
// close block
closeBlock()
// a block is always going to be patched, so track it as a child of its
// parent block
if (isBlockTreeEnabled > 0 && currentBlock) {
currentBlock.push(vnode)
}
return vnode
}
我们再看上面提到的patchUnkeyedChildren方法,里面都用到了判断,证明只有这些标记的才会参与到diff比较,静态的不会比较
if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
patchKeyedChildren(){}
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
patchUnkeyedChildren()
}
patchflags具体有哪些标志,点我看源码
/**
*
* Patch flags can be combined using the | bitwise operator and can be checked
* using the & operator, e.g.
*
* ```js
* const flag = TEXT | CLASS
* if (flag & TEXT) { ... }
* ```
*/
export const enum PatchFlags {
TEXT = 1,
CLASS = 1
上面Flag都是使用
1
再看源码中判断
patchFlag & PatchFlags.KEYED_FRAGMENT
KEYED_FRAGMENT = 1