×

Vue3 四大宏函数,这篇讲透了!(附避坑指南)

独孤求败 独孤求败 发表于2026-07-30 00:01:19 浏览42 评论0

抢沙发发表评论

做 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()

📊 一张表速查

宏函数
方向
核心用途
备注
defineProps
父→子
接收数据、配置属性
TS 用 withDefaults
defineEmits
子→父
触发回调、传递结果
TS 用元组约束参数
defineModel
双向
v-model 双向绑定
⚠️ Vue 3.4+
defineExpose
父调子
ref 调用内部方法
默认封闭,需主动暴露

🚨 面试 & 实战易错总结

  1. 1. Props 解构:直接解构 = 丢失响应式,认准 toRefs
  2. 2. TS 默认值:interface 里写 default 无效,请用 withDefaults
  3. 3. defineModel:Vue 3.3 及以下不支持,老项目别硬上。
  4. 4. Ref 为空:script setup 默认不暴露,忘记 defineExpose 就是坑。
  5. 5. Emit 警告:未声明的事件触发会报警告,TS 下还会报错。


群贤毕至

访客