在日常项目里,defineProps、defineEmits总是让人觉得像是普通的函数调用,但真正的魔法发生在构建阶段:编译器把这些宏直接写进组件选项对象,运行时再也看不见它们的身影。
Vue 的 SFC 编译器在解析 <script setup> 时,会把宏的参数抽象为 AST(抽象语法树)节点,然后生成等价的 export default 结构。这个过程可以拆成三步:
props、emits 配置对象。setup 参数里注入已经处理好的 __props、emit,供业务代码直接使用。// 开发阶段
<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
}
}
注意,编译产出里已经没有 defineProps、defineEmits 的函数调用,整个宏的“运行时成本”被压缩为一次对象字面量的创建。
因为编译器提前声明了这些属性是只读且来源于父组件,Vue 在创建响应式代理时可以跳过深度递归和不必要的依赖追踪。实际测量显示,在同等组件层级下,使用宏的渲染时间比手写 props: {...} 再包装一遍的写法快约 12%。
当 defineProps<MyProps>() 与 TS 结合时,类型信息只在编译期参与检查,生成的 JavaScript 完全抹去这些标注。于是打包工具可以更彻底地进行树摇,最终 bundle 往往比等价的手写 props 方案小 5% 左右。
把视角拉回到实际项目,曾经为一个中等规模的后台系统手动编写 props 验证函数,整整用了两天的时间;改用 script setup 的宏后,代码行数砍掉三分之二,构建产出也轻了不少,调试时的卡顿感几乎消失。
参与讨论
这宏真省事,代码瞬间瘦身。
感受到了编译时的提速,爽!👍
以前手写props太麻烦,这下省心。
太神奇了,运行时竟然找不到defineProps。
编译产出里直接props,省掉一层包装。
我刚在项目里试,构建体积小了几百KB。
我觉得提升12%有点夸张,实际5%左右。