1. 初始化项目
cmd
npm create vite
生成项目后,文件目录如下:
csharp
├── 📁 .idea/ # IntelliJ IDEA 配置目录
├── 📁 .vscode/ # VS Code 配置目录
├── 📁 public/ # 静态资源目录
│ └── vite.svg # Vite 默认图标
├── 📁 src/ # 源代码目录
│ ├── 📁 assets/ # 项目资源文件
│ │ └── vue.svg # Vue 图标
│ ├── 📁 components/ # Vue 组件目录
│ │ └── HelloWorld.vue # Hello World 组件
│ ├── App.vue # 主应用组件
│ ├── main.ts # 应用入口文件
│ ├── style.css # 全局样式文件
│ └── vite-env.d.ts # Vite 环境类型声明
├── index.html # HTML 入口文件
├── package.json # 项目配置和依赖管理
├── README.md # 项目说明文档
├── tsconfig.json # TypeScript 基础配置
├── tsconfig.app.json # TypeScript 应用配置
├── tsconfig.node.json # TypeScript Node.js 配置
├── vite.config.ts # Vite 构建工具配置
└── .gitignore # Git 忽略文件配置
2. 打造自己的组件,以button为例
Button.vue
html
<template>
<button
ref="_buttonRef"
class="ms-button"
:disabled=disabled
:autofocus=autofocus
:type=nativeType
:class="{
[`ms-button--${type}`]: type,
[`ms-button--${size}`]: size,
'is-plain': plain,
'is-round': round,
'is-circle': circle,
'is-disabled': disabled,
}">
<slot></slot>
</button>
</template>
<script setup lang="ts">
import {type ButtonProps} from "./type";
import {onMounted, ref} from "vue";
const _buttonRef = ref<HTMLButtonElement>();
onMounted(()=>{
console.log('buttonRef === ', _buttonRef.value)
})
//宏函数
defineOptions({
name: 'MsButton'
})
withDefaults(defineProps<ButtonProps>(),{
nativeType: 'button'
})
defineExpose({
buttonRef: _buttonRef
})
</script>
<style scoped>
</style>
type.ts
ts
export type ButtonType = 'primary' | 'info' | 'success' | 'warning' | 'danger';
export type ButtonSize = 'large' | 'small';
export type NativeType = 'button' | 'submit' | 'reset'
export interface ButtonProps {
type?: ButtonType,
size?: ButtonSize,
nativeType?: NativeType,
plain?: boolean,
round?: boolean,
circle?: boolean,
disabled?: boolean,
autofocus?: boolean,
}
宏函数
在 Vue 3 中,宏函数(Macros) 是特殊的编译时函数,它们在代码被浏览器执行前就被 Vue 编译器处理,主要服务于 <script setup>
语法糖。这些函数不是普通的 JavaScript 函数,而是 Vue 编译器识别的特殊标记,用于声明组件选项或执行编译时优化。
以下是 Vue 3 的核心宏函数详解:
一、宏函数的本质特性
- 编译时处理
在构建阶段被 Vue 编译器转换,不会出现在最终输出代码中 - 仅限顶层作用域
必须直接在<script setup>
的顶层使用,不可嵌套在函数内 - 类型安全
完美支持 TypeScript 类型推导(如 props/emits 的类型检查) - 零运行时开销
编译后会被移除,不增加生产包体积
二、核心宏函数详解
1. defineProps
- 声明组件 Props
vue
<script setup lang="ts">
// 类型声明式(推荐)
const props = defineProps<{
title: string
size?: 'small' | 'medium' | 'large'
disabled: boolean
}>()
// 运行时声明式
const props = defineProps({
title: { type: String, required: true },
size: { type: String, default: 'medium' },
disabled: Boolean
})
</script>
编译后效果 :
👉 转换为标准的 props
组件选项
👉 生成类型安全的 Props 验证代码
2. defineEmits
- 声明组件事件
vue
<script setup lang="ts">
// 类型声明式(推荐)
const emit = defineEmits<{
(e: 'update:title', value: string): void
(e: 'confirm'): void
}>()
// 运行时声明式
const emit = defineEmits(['update:title', 'confirm'])
</script>
用法:
js
emit('update:title', 'New Title') // 触发事件
3. defineExpose
- 暴露组件公共属性
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
const reset = () => { count.value = 0 }
// 暴露给父组件
defineExpose({
count,
reset
})
</script>
父组件调用:
vue
<template>
<ChildComponent ref="child" />
</template>
<script setup>
const child = ref(null)
child.value.reset() // 调用子组件方法
</script>
4. defineOptions
(Vue 3.3+) - 设置组件选项
vue
<script setup>
defineOptions({
name: 'CustomComponent',
inheritAttrs: false,
// 自定义选项
customOption: 'value'
})
</script>
5. defineSlots
(Vue 3.3+) - 类型化插槽
vue
<script setup lang="ts">
defineSlots<{
default: (props: { msg: string }) => any
header?: () => VNode
}>()
</script>
三、宏函数 vs 普通函数
特性 | 宏函数 (如 defineProps ) |
普通函数 (如 ref ) |
---|---|---|
执行时机 | 编译阶段处理 | 运行时执行 |
代码位置 | 必须位于 <script setup> 顶层 |
可出现在任意位置 |
类型支持 | 完整 TS 类型推导 | 需手动标注类型 |
输出结果 | 被编译器替换为选项 API | 生成实际 JavaScript 代码 |
可组合性 | 不可组合使用 | 可组合到自定义 hook 中 |
四、工作原理解析
以 defineExpose
为例的编译过程:
flowchart LR
A[源代码] -->|defineExpose| B[编译器]
B --> C[AST 解析]
C --> D[转换为 Options API]
D --> E[输出编译后代码]
输入 (源代码):
vue
<script setup>
defineExpose({ count: 0 })
</script>
输出 (编译后):
js
export default {
setup() {
const __returned__ = { count: 0 }
// 编译器注入的暴露逻辑
Object.assign(__returned__, {
__exposed: __returned__
})
return __returned__
},
// 特殊标记
__expose: true
}
五、最佳实践与注意事项
-
避免在逻辑块中使用
js// ❌ 错误!宏不能在函数内使用 function init() { defineProps({ /*...*/ }) }
-
组合式 API 优先
宏函数应配合
ref
/computed
等组合式 API 使用 -
类型声明优先于运行时声明
使用 TypeScript 时选择类型声明方式:
ts// ✅ 推荐 defineProps<{ title: string }>() // ⚠️ 次选 defineProps({ title: String })
-
与普通函数区分使用
vue<script setup> // 宏函数(编译时) const props = defineProps(...) // 普通函数(运行时) const count = ref(0) </script>
-
版本兼容注意
defineOptions
/defineSlots
需 Vue 3.3+- 旧项目可通过插件
unplugin-vue-define-options
兼容
总结
Vue 3 的宏函数是现代化组件开发的基石,它们:
- 🚀 通过编译时魔法简化组件声明
- 🛡️ 提供类型安全的 Props/Emits 声明
- 📦 实现零运行时开销的 API 设计
- 💡 与组合式 API 协同提升代码组织性
理解宏函数的编译时特性,能帮助开发者更高效地构建健壮且类型安全的 Vue 3 应用。