第一节:
# Diff
静态标记
Vue3 中仅对静态标记标记对象进行比较
<div>
<p>Xmo</p>
<p>{{kkl}}</p>
<p>Xmo</p>
<p>1234</p>
<img :src="img">
</div>
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("p", null, "Xmo"),
_createVNode("p", null, _toDisplayString(_ctx.kkl), 1 /* TEXT */),
_createVNode("p", null, "Xmo"),
_createVNode("p", null, "1234"),
_createVNode("img", { src: _ctx.img }, null, 8 /* PROPS */, ["src"])
]))
}
// Check the console for the AST
可以看到,源码中,对 msg 设计了静态标记,这里是1,后面跟注释 TEXT ,代表这个标签的 TEXT 数据是会动态变化的。(动态变化的东西反而叫静态标记,可还行)
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
- Vue2 中的虚拟dom是进行全量的杜比
- Vue3 新增了静态标记(PatchFlag)
- 只比对带有 PF 的节点
- 并且通过 Flag 的信息得知当前节点要比对的具体内容
静态提升
- vue2中无论元素是否参与更新,每次都会重新创建,然后在渲染
- vue3中对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
对上面的代码开启静态提升(hoistStatic)
开启位置
开启后的效果
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "Xmo", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "Xmo", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "Xmo", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_createVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
_createVNode("img", { src: _ctx.srci }, null, 8 /* PROPS */, ["src"])
]))
}
// Check the console for the AST
将静态内容提取出来,变成常量再进行赋值,只需创建一次,后面直接复用,
非静态提升:每次创建都要重新创建
静态提升:静态内容只需创建一次,后面直接复用
- 以后每次进行render的时候,就不会重复创建这些静态的内容,而是直接从一开始就创建好的常量中取就行了。
#事件侦听器缓存
写一段带事件的代码
<div>
<button @click="onClick">btn</button>
</div>
import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", { onClick: _ctx.onClick }, "btn", 8 /* PROPS */, ["onClick"])
]))
}
// Check the console for the AST
这里我们还没有开启事件监听缓存,熟悉的静态标记 8 /* PROPS */
出现了,它将标签的 Props (属性) 标记动态属性。
如果我们存在属性不会改变,不希望这个属性被标记为动态,那么就需要 cacheHandler 的出场了。
开启后
import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick && _ctx.onClick(...args)))
}, "btn")
]))
}
// Check the console for the AST
_createVnode 的第二个属性,从
{ onClick: _ctx.onClick }
变为了
{ onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args))) }
它的意思很明显,onClick 方法被存入 cache。在使用的时候,如果能在缓存中找到这个方法,那么它将直接被使用。如果找不到,那么将这个方法注入缓存。总之,就是把方法给缓存了。
如果有多个标签、方法,效果
<div>
<button @click="onClick" @mouseover="onMouseover">btn</button>
<button @click="onClick1">btn</button>
</div>
import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick && _ctx.onClick(...args))),
onMouseover: _cache[2] || (_cache[2] = (...args) => (_ctx.onMouseover && _ctx.onMouseover(...args)))
}, "btn", 32 /* HYDRATE_EVENTS */),
_createVNode("button", {
onClick: _cache[3] || (_cache[3] = (...args) => (_ctx.onClick1 && _ctx.onClick1(...args)))
}, "btn")
]))
}
// Check the console for the AST
值得注意的是,在测试过程中,只要监听了除了 click 以外的方法,都会添加 32 /* HYDRATE_EVENTS */
事件监听静态标记(事件的方法静态的,但监听的事件则是动态的【onMouseover是缓存的,而mouseover事件则不是】)。
总之,静态提升之后,事件就不会在 diff 算法中进行比较了。
patchFlags 是什么
export const enum PatchFlags {
// 动态文字内容
TEXT = 1,
// 动态 class
CLASS = 1 << 1,
// 动态样式
STYLE = 1 << 2,
// 动态 props
PROPS = 1 << 3,
// 有动态的key,也就是说props对象的key不是确定的
FULL_PROPS = 1 << 4,
// 合并事件
HYDRATE_EVENTS = 1 << 5,
// children 顺序确定的 fragment
STABLE_FRAGMENT = 1 << 6,
// children中有带有key的节点的fragment
KEYED_FRAGMENT = 1 << 7,
// 没有key的children的fragment
UNKEYED_FRAGMENT = 1 << 8,
// 只有非props需要patch的,比如`ref`
NEED_PATCH = 1 << 9,
// 动态的插槽
DYNAMIC_SLOTS = 1 << 10,
// SPECIAL FLAGS -------------------------------------------------------------
// 以下是特殊的flag,不会在优化中被用到,是内置的特殊flag
// 表示他是静态节点,他的内容永远不会改变,对于hydrate的过程中,不会需要再对其子节点进行diff
HOISTED = -1,
// 用来表示一个节点的diff应该结束
BAIL = -2,
}