编译器提取静态属性是在transform阶段通过确定性分析识别永不变化的HTML属性并剥离出运行时patch流程。其判定标准包括:值为纯字符串、布尔原生属性或完全静态绑定;不含响应式变量、插值等;不与动态属性混用同名属性;批量绑定中任一动态则整组失效。transformHoist插件负责遍历AST、校验并聚合静态属性为plain object,作为createVNode第二参数直接传入,使其脱离响应式上下文、不参与diff和更新。

编译器提取静态属性,本质是**在模板解析后的 transform 阶段,通过确定性分析识别出永不变化的 HTML 属性,并将它们从运行时 patch 流程中剥离出来**。这并非运行时判断,而是一次编译期的“提前承诺”:只要属性值在构建时就能被断定为恒定,它就不再参与后续 diff 和更新。
静态属性的判定标准很明确
一个属性要被识别为静态,必须同时满足:
- 值是纯字符串字面量(如
class="btn submit")、布尔原生属性(如disabled、required),或带绑定但内容完全静态(如:class="'fixed'") - 不包含任何响应式变量、计算属性、插值表达式(如
{{id}})、指令参数(如v-if="show") - 不与动态属性混用在同一属性名下(例如
class="a" :class="b"会让整个 class 被判为动态) - 批量绑定(
v-bind="{id: 'x', class: 'y'}")中任一值动态,整组失效
transformHoist 的核心任务不是“提升变量”,而是“分离动静”
注意:这里的 hoist 并非 JavaScript 中的变量提升(hoisting),也不是 Traceur 那类语法重写。Vue 3 编译器中的 transformHoist 是一个特定 transform 插件,作用是:
- 遍历 AST 中每个 element 节点的 attributes 和 directives
- 对每个属性调用
isStaticArg和isStaticExp等校验函数,逐项打标 - 把所有标记为
static的属性聚合成一个 plain object(如{ class: "btn", id: "ok", disabled: "" }) - 在生成
createVNode调用时,把这个对象作为 第二参数(props)直接传入,且不包裹在 effect 或 patchFlag 逻辑中
为什么这个过程叫 “Hoist”?
因为这些静态 props 被“提升”出了 runtime 的响应式上下文——它们不再触发依赖收集,不参与 patch 对比,也不随组件 re-render 重新计算。生成的 vnode 中,它们就像写死的常量一样存在于 props 字段,由编译器一次性注入,之后彻底“视而不见”。
你写的代码如何影响 hoist 效果?
写法会直接影响编译器能否识别静态性:
- 用
class="a b"比:class="'a b'"更稳妥(后者需额外走 binding 分析路径) - 布尔属性写成
disabled,别写:disabled="true" - 避免
class="x" :class="{ y: active }"这种混合写法,改用:class="['x', { y: active }]"让 class 整体可控 - 多个静态属性尽量拆开写,别塞进一个
v-bind
真正起作用的不是某一行魔法配置,而是编译器在 transform 阶段对 AST 节点做的静态语义分析。只要你写的模板足够“可判定”,它就会被干净地 hoist 出来,成为 render 函数里一段不动如山的字面量。

















