Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 task
masterX89 opened this issue May 17, 2022 · 0 comments
Open
1 task
Labels
compiler-core Problems related to compiler-core performance Problems related to performance runtime-core Problems related to runtime-core

Comments

@masterX89
Copy link
Owner

问题

说一说 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 的大局观。仅以本篇鼓励所有学习的小伙伴,带着问题看源码,你会收获更多。

@masterX89 masterX89 added compiler-core Problems related to compiler-core runtime-core Problems related to runtime-core performance Problems related to performance labels May 17, 2022
@masterX89 masterX89 changed the title Vue3 中编译优化方面的*静态提升*是什么以及为何可以优化? Vue3 中编译优化方面的静态提升是什么以及为何可以优化? May 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler-core Problems related to compiler-core performance Problems related to performance runtime-core Problems related to runtime-core
Projects
None yet
Development

No branches or pull requests

1 participant