做 Vue3 开发,没人能绕开 <script setup>。
作为官方主推的语法糖,它简洁、高效,是企业项目的标配。但很多人只懂皮毛,一遇到 TS 类型校验、双向绑定、ref 调用 就频频翻车。
其实,用好 <script setup> 的核心就一句话:掌握四大编译宏函数。
今天这篇全场景实战手册,不讲废话,直接上代码 + 避坑指南,建议收藏备用!👇
💡 前置认知:什么是编译宏?
先记住 3 个关键点,避免只会抄代码不懂原理:
• 无需 import:它们是编译时宏,仅在 <script setup>生效。• 零运行时开销:编译阶段处理完毕,性能优于 Options API。 • TS 完美适配:支持完整类型推导,组件封装神器。
🎯 四大宏分工一览
• defineProps:父 → 子(接收数据)• defineEmits:子 → 父(触发事件)• defineModel:父子双向绑定(v-model 神器)• defineExpose:父调子(暴露方法/属性)
1️⃣ defineProps:父传子全场景
✅ JS 基础写法
适合非 TS 项目,支持类型、必填、默认值、自定义校验:
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 10 },
score: {
type: Number,
default: 60,
validator: (val) => val >= 0 && val <= 100
}
})✅ TS 精准写法(推荐)
配合泛型 + withDefaults,类型约束更严格:
interface Props {
title: string
count?: number
list?: string[]
}
// ⚠️ 注意:TS 下设置默认值必须用 withDefaults
const props = withDefaults(defineProps<Props>(), {
count: 0,
list: () => [] // 引用类型必须用函数返回
})⚠️ 高频避坑
• 解构丢失响应式:禁止直接 const { title } = props,请用toRefs(props)。• 引用类型默认值:数组/对象必须用工厂函数 () => [],否则多实例共享数据。• TS 默认值:不能写在 interface 里,必须搭配 withDefaults。
2️⃣ defineEmits:子传父全场景
✅ JS 基础写法
const emit = defineEmits(['updateCount', 'submit'])
emit('updateCount', 100)
emit('submit', { name: 'Vue3', age: 3 })✅ TS 严格类型写法
精准约束事件名 + 参数类型,杜绝传参错误:
const emit = defineEmits<{
close: [] // 无参
updateCount: [number] // 单参数
submit: [{ name: string; age: number }] // 对象参数
}>()
// ✅ 类型自动校验,传错直接报错
emit('updateCount', 20)💡 核心规范
• 所有自定义事件必须先声明,否则控制台警告。 • 支持一次性声明多个事件,适配复杂回调场景。
3️⃣ defineModel:双向绑定终极方案 🔥
⚠️ 版本要求:Vue 3.4+ 专属,低版本请用传统 props + emit。
这是 Vue3.4 的王炸更新,一行代码替代 props + emit 组合拳,代码量减少 80%!
✅ 基础用法
// 子组件:自带响应式,直接 v-model
const modelValue = defineModel()<!-- 父组件:原生体验 -->
<Child v-model="inputValue" />✅ 多双向绑定 + TS 类型
// 自定义名称 + 类型约束 + 默认值
const title = defineModel<string>('title')
const num = defineModel<number>('num', { default: 0 })<!-- 父组件 -->
<Child v-model:title="pageTitle" v-model:num="pageNum" />💡 为什么用它?
• 无需手动定义 update:xxx事件。• 原生响应式,修改即同步。 • 封装 Input、Select 等表单组件的首选方案。
4️⃣ defineExpose:父调子必备
setup 默认是封闭的!父组件通过 ref 拿不到任何东西,必须主动暴露。
✅ 基础用法
const count = ref(0)
const reset = () => { count.value = 0 }
// 主动暴露
defineExpose({ count, reset })✅ TS 类型强化
export interface ExposeType {
count: number
reset: () => void
}
defineExpose<ExposeType>({ count: count.value, reset })⚠️ 高频避坑
• 默认封闭:别忘了 expose,否则 ref 拿到的是空对象。 • 最小暴露原则:只暴露必要内容,保证封装性。 • 安全调用:父组件调用时务必加可选链 childRef.value?.reset()。
📊 一张表速查
definePropsdefineEmitsdefineModeldefineExpose
🚨 面试 & 实战易错总结
1. Props 解构:直接解构 = 丢失响应式,认准 toRefs。2. TS 默认值:interface 里写 default 无效,请用 withDefaults。3. defineModel:Vue 3.3 及以下不支持,老项目别硬上。 4. Ref 为空:script setup 默认不暴露,忘记 defineExpose就是坑。5. Emit 警告:未声明的事件触发会报警告,TS 下还会报错。