Skip to content

Targeted update (靶向更新)

指更新节点的时候不必全量更新,而是针对性地更新某些节点。

流程

  1. 模板编译阶段 找出动态节点,并打上patchFlag 标记
  2. 运行时分为两个阶段
    • render:找出所有被标记的动态节点 放到block节点的dynamicChildren
    • 更新:从block节点的dynamicChildren 中找出需要更新的节点,基于patchFlag 的值进行更新

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)
  }
}