Skip to content

Vue3 中编译优化方面的静态提升是什么以及为何可以优化? #3

Open
@masterX89

Description

@masterX89

问题

说一说 Vue3 中编译优化方面的 静态提升 是什么?以及为什么使用 静态提升 可以编译优化?

该问题可能引申自:

Vue3 中有哪些 编译优化 手段?

分析

案例来自于《Vuejs 设计与实现》

假如有如下 template 1:

<div>
  <p>static text</p>
  <p>{{ message}}</p>
</div>

以及如下的 template 2:

<div>
  <p foo='foo'>{{ message }}</p>
</div>

对于 template 1,如果没有使用静态提升,渲染函数最后会变成:

const { createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue

return function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("p", null, "static text"),
    _createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ]))
}

如果响应式数据 message 发生了变化,render 函数会重新执行。但对于 'static text'p 标签而言,额外的创建行为会带来性能消耗。

同理对于 template 2,如果没有使用静态提升,渲染函数最后会变成:

const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue

return function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("p", { foo: "foo" }, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ]))
}

这里给出 renderer 的部分相关代码

function patchElement(n1, n2, container, parentComponent, anchor) {
  const el = (n2.el = n1.el)
  // children
  patchChildren(n1, n2, el, parentComponent, anchor)
  // props
  const oldProps = n1.props || EMPTY_OBJ
  const newProps = n2.props || EMPTY_OBJ
  patchProps(el, oldProps, newProps)
}

function patchProps(el, oldProps, newProps) {
  // #5857
  if (oldProps !== newProps) {
    // 省略 newProps 的处理代码
    // 省略 oldProps 的处理代码
  }
}

可以看到 patchElement 会执行 patchProps,而 { foo: "foo" } !== { foo: "foo" } 会返回 true ,因此 newPropsoldProps 的处理代码都会执行,额外的处理行为无疑会带来性能消耗。

解决方案

template 1

对于 template 1 的场景而言,响应式数据的更新影响到了 静态节点,导致其重复创建。因此将其提升至 render 函数外,做一个引用即可:

const { createElementVNode: _createElementVNode, toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue

const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "static text", -1 /* HOISTED */)

return function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _hoisted_1,
    _createElementVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ]))
}

template 2

同理 template 1 的场景中,只需要将 props 提升到 render 函数外

const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue

const _hoisted_1 = { foo: "foo" }

return function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("p", _hoisted_1, _toDisplayString(_ctx.message), 1 /* TEXT */)
  ]))
}

patchProps 时候,_hoisted_1 !== _hoisted_1 返回 false,就不会进行多余的 props 对比和更新了。

回答

说一说 Vue3 中编译优化方面的 静态提升 是什么?以及为什么使用 静态提升 可以编译优化?

静态提升指的是:

  1. 对于静态的树(节点),使用类似如下的代码,将其提升到 render 函数外,即是 静态树(节点)的提升

    const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, "static text", -1 /* HOISTED */)

    可以避免其他不相干响应式数据更新触发 render ,导致静态树(节点)重复创建的问题

  2. 对于动态的树(节点),如果其 props 是静态的,使用如下的代码,将其提升到 render 函数外,即是 静态 props 的提升

    const _hoisted_1 = { foo: "foo" }

    可以避免在 patchProps 阶段,多余的 newPropsoldProps 的处理逻辑

不足

  • hoistStatic 的代码还没有看,实现部分要以后补上

后记

为什么要写这一篇呢?因为在 runtime-core 部分看到 patchProps 的代码时候,崔大说了句:为了性能加个 if (oldProps !== newProps) 语句,当时我就很不解,看了 HCY 的书,甚至还在 vue 3 里提了个 issue #5773,认为判断应该改为 hasPropsChanged。因为形如下述的这个 case:

it('should not update element props which is already mounted when props are same', () => {
  render(h('div', { id: 'bar' }, ['foo']), root)
  expect(inner(root)).toBe('<div id="bar">foo</div>')

  render(h('div', { id: 'bar' }, ['foo']), root)
  expect(inner(root)).toBe('<div id="bar">foo</div>')
})

oldProps !== newProps 无论如何都会都会返回 true 的。

而群里的小伙伴 Salt 则直接提了个 PR #5857,认为这个判断是一个无效语句,应当删除。

后来 Evan You 回复了PR #5857,指出了 静态提升 的问题,这个判断是 有意为之。

所以我写了这篇文章,以便解答后续的小伙伴在看到 patchProps 的时候产生的困惑。产生困惑是正常的!因为此时眼光只局限在了 runtime-core 中,而没有 compiler-core 的大局观。仅以本篇鼓励所有学习的小伙伴,带着问题看源码,你会收获更多。

Metadata

Metadata

Assignees

No one assigned

    Labels

    compiler-coreProblems related to compiler-coreperformanceProblems related to performanceruntime-coreProblems related to runtime-core

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions