编译时宏是Vue3在<script setup>中引入的特殊语法结构,在编译阶段被处理替换而非运行时执行。


主要包括defineProps、defineEmits等宏函数,具有以下特点:

1)无需显式导入,由编译器自动注入;

2)提供类型安全校验;

3)编译后代码会被优化移除;

4)相比运行时函数具有更好的开发体验和性能优化。


这些宏仅适用于<script setup>语法,在传统选项式API中仍需使用常规导入方式。


典型应用场景包括定义组件props、emits等,能显著简化组件定义代码并提升开发效率。


编译时宏(Compile-time Macros)

1. 基本概念

编译时宏是在代码编译阶段被处理/替换的特殊语法结构,而不是在运行时执行的函数。


比喻理解

  • 普通函数:像餐厅的厨师,客人点餐(运行时)才开始做菜

  • 编译时宏:像食品加工厂,在食材送到餐厅前就已经预处理好了


2. Vue 中的编译时宏示例

defineProps - 最典型的例子

// 编译前(开发者写的代码)
<script setup lang="ts">
const props = defineProps<{
  title: string;
  count: number;
}>();
</script>

// 编译后(浏览器实际运行的代码)
<script>
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    title: {
      type: String,
      required: true
    },
    count: {
      type: Number,
      required: true
    }
  },
  setup(props) {
    // setup 函数内容...
    return {};
  }
});
</script>

3. 编译时宏 vs 运行时函数

特性编译时宏运行时函数
执行时机编译阶段运行阶段
导入需求不需要导入需要导入
类型检查编译时检查运行时检查
代码存在编译后被替换保留在最终代码中
示例defineProps, defineEmitsref, computed, onMounted

4. Vue 3 中的编译时宏列表

核心宏

// 都不需要导入!
defineProps()      // 定义组件 props
defineEmits()      // 定义组件 emits
defineExpose()     // 暴露组件实例属性
defineOptions()    // 定义组件选项(Vue 3.3+)
defineSlots()      // 定义插槽类型(Vue 3.3+)
defineModel()      // 双向绑定(Vue 3.4+)
withDefaults()     // 为 props 设置默认值

特殊宏

// 这些也是编译时处理
const __sfc__ = ...  // 单文件组件编译标识

5. 编译过程详解

阶段1:源码(开发者编写)

<script setup lang="ts">
const props = defineProps<{ msg: string }>();
const emit = defineEmits<{ (e: 'click'): void }>();
</script>

阶段2:编译转换(Vue 编译器)

// 编译器进行以下转换:
// 1. 提取 defineProps 的类型信息
// 2. 生成对应的 props 选项
// 3. 生成对应的 emits 选项
// 4. 移除 defineProps/defineEmits 调用

阶段3:生成代码(浏览器运行)

import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    msg: { type: String, required: true }
  },
  emits: ['click'],
  setup(props, { emit }) {
    // 原来的 setup 逻辑
    return {};
  }
});

6. 为什么需要编译时宏?

优点1:更好的开发者体验

// 简洁的写法
const props = defineProps<{ title: string }>();

// 而不是繁琐的
export default defineComponent({
  props: {
    title: { type: String, required: true }
  },
  setup(props) {
    // ...
  }
});

优点2:类型安全

// TypeScript 可以在编译时检查
defineProps<{
  title: string;
  count: number;
  optional?: boolean;  // ✅ 类型安全
}>();

// 错误会被立即发现
const props = defineProps<{ title: string }>();
console.log(props.titel);  // ❌ 编译时报错:属性 'titel' 不存在

优点3:性能优化

// 编译时可以进行的优化:
// 1. 移除类型信息(减少打包体积)
// 2. 静态分析优化
// 3. 提前错误检测

7. 实际编译示例

示例1:完整组件编译

<!-- 编译前 -->
<script setup lang="ts">
interface Props {
  title: string;
  count?: number;
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
});

const emit = defineEmits<{
  (e: 'update:title', value: string): void;
  (e: 'submit'): void;
}>();
</script>

<!-- 编译后(简化版) -->
<script>
export default {
  props: {
    title: { type: String, required: true },
    count: { type: Number, default: 0 }
  },
  emits: ['update:title', 'submit'],
  setup(props, { emit }) {
    // ...setup 逻辑
  }
}
</script>

示例2:defineExpose 编译

<!-- 编译前 -->
<script setup>
const count = ref(0);
const increment = () => count.value++;

defineExpose({
  increment,
  reset: () => count.value = 0
});
</script>

<!-- 编译后 -->
<script>
export default {
  setup() {
    const count = ref(0);
    const increment = () => count.value++;
    
    // 编译器会处理 expose 逻辑
    return {
      count,
      increment
    };
  },
  // 暴露给模板编译器的信息
  expose: ['increment', 'reset']
}
</script>

8. 与其他框架的对比

React

// React 没有类似的编译时宏
// Props 类型是纯 TypeScript 接口
interface Props {
  title: string;
}

const Component: React.FC<Props> = ({ title }) => {
  return <div>{title}</div>;
};

Svelte

<!-- Svelte 也有编译时特性,但语法不同 -->
<script lang="ts">
  // 导出变量 = props
  export let title: string;
  export let count = 0; // 默认值
</script>

9. 常见问题

Q1:为什么 IDE 不报错,但运行时出错?

// IDE 认识 defineProps,因为它有类型定义
// 但如果你手动导入,会导致冲突
import { defineProps } from 'vue'; // ❌ 不要这样做!

// Vue 的类型声明文件中定义了这些宏
// 所以 TypeScript 知道它们存在

Q2:如何查看编译结果?

# 使用 Vue 的编译工具
vue-compile MyComponent.vue

# 或者查看构建输出
npm run build -- --verbose

Q3:可以自定义编译时宏吗?

// 一般情况下不可以
// 但可以通过插件系统扩展
// 比如 unplugin-vue-macros 提供了更多宏

10. 总结要点

  1. 编译时执行:在构建阶段被处理,不出现在最终代码中

  2. 无需导入:Vue 编译器自动识别和处理

  3. 类型安全:提供 TypeScript 类型检查

  4. 语法糖:简化组件定义,提升开发体验

  5. 性能优化:编译时可进行静态分析和优化


记住:所有 define* 开头的函数在 <script setup> 中都是编译时宏,不要导入它们!


为什么不需要导入?

1. 编译时宏

  • defineProps、defineEmits、defineExpose、withDefaults 等都是 Vue 的编译时宏

  • 它们在编译阶段被 Vue 编译器处理,不会出现在运行时

  • 编译器会自动注入正确的实现

2. 自动注入

在 <script setup> 中,Vue 编译器会自动注入这些函数:

// 编译前
<script setup>
const props = defineProps({...})
</script>

// 编译后
<script>
import { defineComponent } from 'vue'
export default defineComponent({
  props: {...},
  setup() {
    // ...
  }
})
</script>

如果需要导入的情况

只有在使用 JS/TS API 而不是 <script setup> 时,才需要导入。


选项式 API

import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    isOpen: Boolean
  },
  emits: ['close-modal'],
  setup(props, { emit }) {
    const closeModal = () => {
      emit('close-modal')
    }
    return { closeModal }
  }
})

组合式 API(非 <script setup>)

import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    isOpen: Boolean
  },
  emits: ['close-modal'],
  setup(props, { emit }) {
    const closeModal = () => {
      emit('close-modal')
    }
    return { closeModal }
  }
})

其他编译时宏

以下这些也不需要导入:

  • defineProps - 定义 props

  • defineEmits - 定义 emits

  • defineExpose - 暴露组件实例的属性

  • defineOptions - 定义组件选项(Vue 3.3+)

  • defineSlots - 定义插槽类型(Vue 3.3+)

  • defineModel - 定义双向绑定(Vue 3.4+)

  • withDefaults - 为 props 提供默认值


总结

场景是否需要导入原因
<script setup>❌ 不需要编译时宏,自动注入
选项式 API✅ 需要使用 Vue 的运行时 API
组合式 API(setup函数)✅ 需要使用 Vue 的运行时 API
Type-only 宏(defineProps等)❌ 不需要纯类型声明,编译时处理
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐