Targeted update (靶向更新)
指更新节点的时候不必全量更新,而是针对性地更新某些节点。
流程
- 模板编译阶段 找出动态节点,并打上
patchFlag
标记 - 运行时分为两个阶段
- render:找出所有被标记的动态节点 放到
block
节点的dynamicChildren
中 - 更新:从
block
节点的dynamicChildren
中找出需要更新的节点,基于patchFlag
的值进行更新
- render:找出所有被标记的动态节点 放到
patchFlags
也是基于位移操作来做的
TIP
所有的patchFlag
可以看vue源码
编译时
html
<div>
<h1>Hello Rika</h1>
<span>{{name}}</span>
</div>
这段代码会编译成
javascript
import { createElementBlock as _createElementBlock, createElementVNode as _createElementVNode, openBlock as _openBlock, toDisplayString as _toDisplayString } from 'vue'
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock('div', null, [
_createElementVNode('h1', null, 'Hello Rika'),
_createElementVNode('span', null, _toDisplayString(_ctx.name), 1 /* TEXT */)
]))
}
// Check the console for the AST
render
会打开一个block
并开始收集动态节点
typescript
let currentBlock = null
// 打开一个block 开始收集动态节点
export function openBlock() {
currentBlock = [] // 用于收集动态节点的
}
// 关闭当前block
export function closeBlock() {
currentBlock = null
}
//
export function setupBlock(vnode) {
vnode.dynamicChildren = currentBlock // 当前elementBlock会收集子节点 用当前block来收集
closeBlock()
return vnode
}
// block 有收集虚拟节点的能力
export function createElementBlock(type, props, children, patchFlag?) {
return setupBlock(createVnode(type, props, children, patchFlag))
}
这样就把动态节点收集到对应的block
中了。
更新
有了patchFlag 就可以针对特定的属性进行更新了
typescript
// 在比较元素的时候 针对某个属性去比较
const { patchFlag, dynamicChildren } = n2 // 拿出patchFlag 和 dynamicChildren
if (patchFlag) { // 如果有patchFlag 就可以针对某项属性更新
if (patchFlag & PatchFlags.CLASS) { // 针对class更新
if (oldProps.class !== newProps.class) {
hostPatchProp(el, newProps.class, oldProps.class)
}
}
if (patchFlag & PatchFlags.STYLE) { // 针对style更新
hostPatchProp(el, newProps.style, oldProps.style)
}
}
else { // 针对 props更新
patchProps(oldProps, newProps, el)
}
if (patchFlag & PatchFlags.TEXT) { // 针对文本更新
// 只要儿子是动态的 只比较儿子
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children)
}
}
if (dynamicChildren) { // 如果收集了动态节点 只需要线性比对即可
patchBlockChildren(n1, n2, el, anchor, parentComponent)
}
else { // 否则需要走全量diff
patchChildren(n1, n2, el, anchor, parentComponent)
}
线性比对, 只需要遍历dynamicChildren
,然后对比每个节点即可。
typescript
function patchBlockChildren(n1, n2, el, anchor, parentComponent) {
for (let i = 0; i < n2.dynamicChildren.length; i++) {
patch(n1.dynamicChildren[i], n2.dynamicChildren[i], el, anchor, parentComponent)
}
}