Vue3 中的 Props:父子组件的“传话筒”详解
接下来我们来聊聊 Vue3 中一个超级基础但又极其重要的概念——Props。
如果你已经会用 Vue 写组件,那你一定用过 props
。但你真的完全理解它吗?比如:
- 为什么子组件不能直接改 props?
- 什么时候该用
.sync
或v-model
? - 如何给 props 加类型验证和默认值?
别急!今天我就用比喻,带你彻底搞懂 Vue3 的 Props,让你从“会用”变成“精通”!
什么是 Props?(一句话说清)
Props 就是父组件传给子组件的数据,相当于“父子之间的传话筒”。
想象一下:你爸(父组件)想让你(子组件)去超市买牛奶,他会告诉你:
- 买什么?—— 牛奶
- 买几瓶?—— 2 瓶
- 什么牌子?—— 蒙牛
这些信息,就是 Props!
<!-- 父组件 App.vue -->
<template>
<BuyMilk
item="牛奶"
count="2"
brand="蒙牛"
/>
</template>
<!-- 子组件 BuyMilk.vue -->
<script setup>
// 接收父组件传来的数据
defineProps(['item', 'count', 'brand'])
</script>
<template>
<div>我要去买 {{ count }} 瓶 {{ brand }} {{ item }}!</div>
</template>
输出结果:我要去买 2 瓶 蒙牛 牛奶!
这就是 Props 的最基本用法:父传子,单向数据流。
如何在 Vue3 中定义 Props?
Vue3 推荐使用 <script setup>
+ defineProps()
,它是一个编译宏(不需要引入),写起来超简单!
方式一:数组写法(简单场景)
defineProps(['title', 'content', 'author'])
适合快速原型,但不推荐生产环境用,因为没类型、没默认值。
方式二:对象写法(推荐!)
defineProps({
title: String,
content: String,
author: {
type: String,
default: '匿名用户'
},
likes: {
type: Number,
default: 0
},
isActive: {
type: Boolean,
default: true
}
})
这才是专业写法!支持类型、默认值、必填校验等。
讲解:Props 的“只读性”(为什么不能改?)
这是新手最容易踩的坑!
错误示范:
const props = defineProps(['count'])
// 想要增加数量?
props.count++ // 警告!不要直接修改 props!
为什么不能改?
因为 Props 是“只读”的,就像你爸告诉你“买 2 瓶牛奶”,你不能偷偷改成“买 3 瓶”然后说“爸说的”!
Vue 的设计哲学是:数据流向要清晰。父组件传数据,子组件只能用,不能私自篡改。
那我想改怎么办?—— 用 emit
反向通知!
正确的做法是:子组件通过 emit
告诉父组件:“我想改数据”,然后由父组件决定是否修改。
示例:一个可增减的计数器
<!-- 子组件 Counter.vue -->
<script setup>
const props = defineProps(['count'])
const emit = defineEmits(['update:count'])
const increase = () => {
emit('update:count', props.count + 1)
}
const decrease = () => {
emit('update:count', props.count - 1)
}
</script>
<template>
<div>
<button @click="decrease">-</button>
{{ count }}
<button @click="increase">+</button>
</div>
</template>
<!-- 父组件 App.vue -->
<script setup>
import { ref } from 'vue'
import Counter from './Counter.vue'
const myCount = ref(5)
</script>
<template>
<Counter
:count="myCount"
@update:count="myCount = $event"
/>
</template>
看到了吗?子组件不能自己改,但可以“申请修改”,父组件同意了才算数!
语法糖:v-model
和 .sync
的秘密
上面的 @update:count
写起来有点啰嗦?Vue 提供了语法糖!
方式一:用 v-model
(最常用)
<!-- 父组件 -->
<Counter v-model="myCount" />
<!-- 等价于 -->
<Counter
:count="myCount"
@update:count="myCount = $event"
/>
注意:默认
v-model
对应的是modelValue
和update:modelValue
。
如果你想用别的名字,比如 v-model:count
:
<!-- 子组件 -->
defineProps(['count'])
defineEmits(['update:count'])
<!-- 父组件 -->
<Counter v-model:count="myCount" />
方式二:.sync
修饰符(Vue2 遗留,Vue3 仍支持)
<!-- 父组件 -->
<Counter :count.sync="myCount" />
<!-- 等价于 -->
<Counter
:count="myCount"
@update:count="myCount = $event"
/>
推荐用
v-model
,更现代、更清晰。
Props 高级用法:类型、默认值、验证
1. 支持复杂类型
defineProps({
userInfo: {
type: Object,
default: () => ({ name: '张三', age: 20 })
},
tags: {
type: Array,
default: () => ['前端', 'Vue']
}
})
对象和数组的
default
必须是函数,否则会共享引用!
2. 自定义验证函数
defineProps({
status: {
type: String,
validator(value) {
// 只允许这几个值
return ['active', 'inactive', 'pending'].includes(value)
}
}
})
传错会直接在控制台报错,开发时非常有用!
3. 必填项
defineProps({
requiredId: {
type: Number,
required: true
}
})
如果父组件没传,Vue 会警告你。
常见问题 & 最佳实践
Q1:Props 是响应式的吗?
是的! 只要父组件的数据变了,子组件的 props 会自动更新。
但记住:不要在子组件中直接修改它。
Q2:我可以给 Props 加 ref
吗?
不要这么做!比如:
const myProp = ref(props.count) // 错误!断开了响应式连接
如果你需要基于 props 做计算,用 computed
:
import { computed } from 'vue'
const doubleCount = computed(() => props.count * 2)
最佳实践总结
建议 | 说明 |
---|---|
用对象形式定义 Props | 支持类型、默认值、验证 |
对象/数组 default 写成函数 | 避免引用共享 |
不要直接修改 Props | 用 emit 通知父组件 |
复杂逻辑用 computed 或 watch |
保持响应式 |
用 v-model 简化双向绑定 |
代码更简洁 |
总结:Props 核心要点
概念 | 说明 |
---|---|
作用 | 父组件向子组件传递数据 |
特性 | 单向数据流、只读 |
修改方式 | 子组件 emit ,父组件修改 |
语法糖 | v-model 、.sync 简化写法 |
安全 | 支持类型、默认值、自定义验证 |
最后一句话
Props 是组件通信的“高速公路”,但它是单行道——数据只能从父到子。
想要“反向开车”?请走
emit
的“匝道”,由父组件决定是否放行!
掌握好 Props,你的组件才能既灵活又安全!