文章地址:https://jaufey.notion.site/Static-hoisting-85e751cf04694a47a1b8d9f9f04cfb35


在 JS 中,有一个术语叫做 Hoisting,指在编译期将变量、函数、类的声明提升至作用域顶端。

本篇文章所讲的 Static hoisting 与 Hoisting 有一点相似,但相关性不大。Static hoisting 是 Vue3 在模板编译阶段的一种优化手段,恰好有一个提升的操作,所以名字中包含 hoisting,同时,Static 则表示 hoisting 的对象是静态的。

由问题出发

我是如何感知到 “静态提升” 的?答案是:某项目从 Vue2 升级到 Vue3 后,出现了 “副作用被 cache” 的问题。

问题描述

  1. 假设有个 v-if=“toggle” 控制的 A 组件。
    1. 在 A 组件初始化的时候,徒手在组件元素中绘制了一个圆圈,类似 $el.appendChild(circle)这算是框架内的 UB,只管拉不管擦的脏写
    2. 当 toggle 由 true 切为 false 再切回 true 的时候,即便代码中没有显式指定任何缓存方式,A 组件却仍保留了上一次挂载后所绘制的圆圈。导致每展示 A 组件一次,脏写内容便累加一次。
  2. 此问题只在构建后出现,而不会在开发间出现。脚手架配置为 Vue-cli 默认配置。
  3. 副作用只是累计了副作用中的 DOM 元素,而事件监听等功能则没有被缓存。

在线示例:https://demo-0814-test.vercel.app/

代码:https://github.com/N-index/demo-0814

问题解决

经过排查与请教,发现是 “静态提升” 这个构建期的优化手段导致了这个问题。

解决方案为以下任意一种:

  1. 在构建阶段,关闭静态提升的功能: https://github.com/vuejs/core/issues/5256#issuecomment-1173723516

  2. 在副作用的父容器上添加 ref 属性,使得静态提升越过这个静态节点。

    具体参考:ref 和 key 的编译示例

问题总结

一句话描述 “静态提升” :在模板编译阶段生成渲染函数的过程中,对于静态内容,Vue 将其对应的 vnode 结果 hoist 到了渲染函数外部,而不是每次都渲染都重新生成 vnode。(示例:编译期间的静态提升)

Vue3 基于这样一个假设,才有了这个优化策略:         静态内容不会被改动,每次都会渲染出一样的 DOM,所以直接缓存起来就可以了。