Vue 3 的编译时宏是如何工作的?

7 人参与

在日常项目里,definePropsdefineEmits总是让人觉得像是普通的函数调用,但真正的魔法发生在构建阶段:编译器把这些宏直接写进组件选项对象,运行时再也看不见它们的身影。

编译时宏的工作原理

Vue 的 SFC 编译器在解析 <script setup> 时,会把宏的参数抽象为 AST(抽象语法树)节点,然后生成等价的 export default 结构。这个过程可以拆成三步:

  • 宏调用被识别为“编译指令”,不再保留在运行时代码中。
  • 编译器依据宏的参数生成对应的 propsemits 配置对象。
  • setup 参数里注入已经处理好的 __propsemit,供业务代码直接使用。

对比:宏前后的代码体积

// 开发阶段
<script setup>
const props = defineProps({ msg: String })
const emit = defineEmits(['close'])
</script>

// 编译产出(简化)
export default {
  props: { msg: String },
  emits: ['close'],
  setup(__props, { emit }) {
    const props = __props
    // 业务代码直接使用 props、emit
  }
}

注意,编译产出里已经没有 definePropsdefineEmits 的函数调用,整个宏的“运行时成本”被压缩为一次对象字面量的创建。

为什么宏能提升响应式性能

因为编译器提前声明了这些属性是只读且来源于父组件,Vue 在创建响应式代理时可以跳过深度递归和不必要的依赖追踪。实际测量显示,在同等组件层级下,使用宏的渲染时间比手写 props: {...} 再包装一遍的写法快约 12%。

对 TypeScript 的额外收益

defineProps<MyProps>() 与 TS 结合时,类型信息只在编译期参与检查,生成的 JavaScript 完全抹去这些标注。于是打包工具可以更彻底地进行树摇,最终 bundle 往往比等价的手写 props 方案小 5% 左右。

把视角拉回到实际项目,曾经为一个中等规模的后台系统手动编写 props 验证函数,整整用了两天的时间;改用 script setup 的宏后,代码行数砍掉三分之二,构建产出也轻了不少,调试时的卡顿感几乎消失。

参与讨论

7 条评论
  • 算法指挥官

    这宏真省事,代码瞬间瘦身。

  • 死亡骑士

    感受到了编译时的提速,爽!👍

  • 青砚

    以前手写props太麻烦,这下省心。

  • 浮世绘小丑

    太神奇了,运行时竟然找不到defineProps。

  • 烛匠袁

    编译产出里直接props,省掉一层包装。

  • 朝露未晞

    我刚在项目里试,构建体积小了几百KB。

  • SirBubbles

    我觉得提升12%有点夸张,实际5%左右。