vue3

334 阅读3分钟

更快、更强、更小

更快

节点标记patchFlag

<div>{{ text }}</div>
<div :class="c"></div>
<div :style="s"></div>
<div :name="n"></div>
<div :class="c" :style="s" :name="n">{{ text }}</div>

编译后的render函数:

const _hoisted_1 = ["name"]
const _hoisted_2 = ["name"]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock(_Fragment, null, [
    _createElementVNode("div", null, _toDisplayString(_ctx.text), 1 /* TEXT */),
    _createElementVNode("div", {
      class: _normalizeClass(_ctx.c)
    }, null, 2 /* CLASS */),
    _createElementVNode("div", {
      style: _normalizeStyle(_ctx.s)
    }, null, 4 /* STYLE */),
    _createElementVNode("div", { name: _ctx.n }, null, 8 /* PROPS */, _hoisted_1),
    _createElementVNode("div", {
      class: _normalizeClass(_ctx.c),
      style: _normalizeStyle(_ctx.s),
      name: _ctx.n
    }, _toDisplayString(_ctx.text), 15 /* TEXT, CLASS, STYLE, PROPS */, _hoisted_2)
  ], 64 /* STABLE_FRAGMENT */))
}

render函数执行后生成的Vnode: 下面只展示最后一个节点的vnode, vnode对象中有一个patchFlag属性;

{
    "__v_isVNode": true,
    "__v_skip": true,
    "type": "div",
    "props": {
        "class": "c",
        "style": "color:red;",
        "name": "n"
    },
    "key": null,
    "ref": null,
    "scopeId": null,
    "slotScopeIds": null,
    "children": "text",
    "component": null,
    "suspense": null,
    "ssContent": null,
    "ssFallback": null,
    "dirs": null,
    "transition": null,
    "el": null,
    "anchor": null,
    "target": null,
    "targetAnchor": null,
    "staticCount": 0,
    "shapeFlag": 9,
    "patchFlag": 15,
    "dynamicProps": [
        "name"
    ],
    "dynamicChildren": null,
    "appContext": null
}

最后在diff的时候,patchFlag>0的节点才需要更新,

并且只需要更新patchFlag对应的属性。

故标记patchFlag可以减少diff比较。

hoistStatic 静态提升

如果是静态节点,则将该静态节点作为变量存储

静态提升的元素,只会生成一次vnode,第二次执行render函数生成vnode的时候,直接复用静态提升变量存放的vnode;

故静态提升可以减少render的执行时间。

Vue3:

(function anonymous(
) {
const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue

// 定义两个变量
const _hoisted_1 = /*#__PURE__*/_createElementVNode("title", null, "标题", -1 /* HOISTED */)
const _hoisted_2 = ["onClick"]

return function render(_ctx, _cache) {
  with (_ctx) {
    const { createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock(_Fragment, null, [
      _hoisted_1,
      _createElementVNode("button", { onClick: clickBtn }, _toDisplayString(count), 9 /* TEXT, PROPS */, _hoisted_2)
    ], 64 /* STABLE_FRAGMENT */))
  }
}
})

cacheHandlers 事件监听器缓存

默认情况下onClick会被视为动态绑定,所以每次都会去追踪它的变化,即会进行diff比较, 但是因为是同一个函数,所以没哟必要去追踪它的变化,想办法将它直接缓存起来复用就会提升性能。

<button @click="clickBtn"></button>
const _hoisted_1 = ["onClick"]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("button", { onClick: _ctx.clickBtn }, null, 8 /* PROPS */, _hoisted_1))
}

如上代码可以看到,onClick果然会被视为动态绑定, 它有patchFlag=8,为动态props。 开启cacheHandlers后, patchFlag就不存在了,那么这部分内容也就不会进行diff比较了。


export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("button", {
    onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.clickBtn && _ctx.clickBtn(...args)))
  }))
}

故事件监听器缓存可以减少diff比较。

使用Proxy代替Object.defineProperty实现响应式

Proxy vs Object.defineProperty

更强

Composition API 组合式编程

产生的原因、解决的问题

  • 组件变大,逻辑关注点的列表增长。导致组件难以阅读和理解,尤其对于那些一开始就没有编写这些组件的人来说。

  • setup选项为组合API的入口点
  • setup选项在创建组件之前执行
  • setup执行时尚未创建组件实例
  • setup选项中没有this
  • setup选项是一个函数,第一个参数是props,第二个是context
  • setup中无法访问组件中声明的任何属性——本地状态、计算属性或方法
  • setup函数的返回值

其他

  • Vue2使用插件可以实现类似的功能,但不推荐

  • 不要为了Composition而composition

  • 被定位为高级特性,因为它旨在解决的问题主要出现在大型应用中

  • 我们不打算彻底修改文档来把它作为默认方案 Options API

Teleport(传送标签)

可将teleport标签包裹的元素渲染到指定父节点, 比如可将modal渲染到body节点下

<teleport to="body">
    <div id="modal">
        我是弹层
    </div>
</teleport>

Fragments(片段)

可以有多个根节点

createRenderer

更小

Tree Shaking 按需加载