用最直白、最详细的方式给你讲解父组件传递子组件(Props)!
🎁 Props:父组件给子组件送礼物
🎭 最简单的比喻
想象你是爸爸 (父组件),你的儿子(子组件)需要零花钱和玩具:
👨 爸爸(父组件)
↓ 给钱、给玩具
👦 儿子(子组件)
📦 第一步:最简单的传递
父组件:给东西
vue
<!-- App.vue(爸爸) -->
<template>
<div>
<!-- 🎁 给儿子传递两样东西 -->
<Son name="小明" age="10" />
</div>
</template>
<script>
import Son from './Son.vue'
export default {
components: { Son }
}
</script>
子组件:收东西
vue
<!-- Son.vue(儿子) -->
<template>
<div>
<h2>我叫:{{ name }}</h2>
<p>我今年:{{ age }}岁</p>
</div>
</template>
<script>
export default {
// 📥 声明:我要接收name和age
props: ['name', 'age']
}
</script>
结果:页面显示"我叫:小明,我今年:10岁"
🎯 第二步:传递动态数据
静态 vs 动态
vue
<!-- 父组件 -->
<template>
<div>
<!-- ❌ 静态传递:传的是字符串"10" -->
<Son name="小明" age="10" />
<!-- ✅ 动态传递:传的是变量的值 -->
<Son :name="userName" :age="userAge" />
<!-- 或者写成 v-bind:name -->
<Son v-bind:name="userName" v-bind:age="userAge" />
</div>
</template>
<script>
export default {
data() {
return {
userName: '小明',
userAge: 10
}
}
}
</script>
关键区别:
name="小明"- 传的是字符串"小明":name="userName"- 传的是变量userName的值
🎪 第三步:传递各种类型的数据
vue
<!-- 父组件 -->
<template>
<div>
<Child
:string-data="message"
:number-data="count"
:boolean-data="isVIP"
:array-data="hobbies"
:object-data="user"
:function-data="handleClick"
/>
</div>
</template>
<script>
export default {
data() {
return {
// 字符串
message: 'Hello',
// 数字
count: 100,
// 布尔值
isVIP: true,
// 数组
hobbies: ['篮球', '足球', '游泳'],
// 对象
user: {
name: '张三',
age: 25
}
}
},
methods: {
// 函数
handleClick() {
console.log('点击了!')
}
}
}
</script>
vue
<!-- 子组件 -->
<template>
<div>
<p>字符串:{{ stringData }}</p>
<p>数字:{{ numberData }}</p>
<p>布尔值:{{ booleanData }}</p>
<p>数组:{{ arrayData }}</p>
<p>对象:{{ objectData.name }}</p>
<button @click="functionData">点击我</button>
</div>
</template>
<script>
export default {
props: [
'stringData',
'numberData',
'booleanData',
'arrayData',
'objectData',
'functionData'
]
}
</script>
🔧 第四步:Props验证(推荐写法)
为什么要验证?
想象你开了个商店,顾客要买东西:
- 不验证:顾客给你什么你都收(可能给你假钱)
- 验证:检查钱是不是真的,数量对不对
vue
<!-- 子组件 -->
<script>
export default {
props: {
// 🎯 方式1:只指定类型
name: String,
age: Number,
isVIP: Boolean,
// 🎯 方式2:详细配置
title: {
type: String, // 类型
required: true, // 必须传
default: '默认标题' // 默认值
},
// 🎯 方式3:多种类型
price: {
type: [Number, String], // 可以是数字或字符串
required: true
},
// 🎯 方式4:对象/数组默认值
user: {
type: Object,
default() { // 必须用函数返回
return {
name: '游客',
age: 0
}
}
},
tags: {
type: Array,
default() { // 必须用函数返回
return []
}
},
// 🎯 方式5:自定义验证
score: {
type: Number,
validator(value) {
// 分数必须在0-100之间
return value >= 0 && value <= 100
}
}
}
}
</script>
🎪 第五步:实际案例
案例1:用户卡片组件
vue
<!-- 父组件:App.vue -->
<template>
<div class="app">
<!-- 显示多个用户卡片 -->
<UserCard
v-for="user in users"
:key="user.id"
:name="user.name"
:age="user.age"
:avatar="user.avatar"
:is-vip="user.isVIP"
/>
</div>
</template>
<script>
import UserCard from './UserCard.vue'
export default {
components: { UserCard },
data() {
return {
users: [
{ id: 1, name: '张三', age: 25, avatar: 'avatar1.jpg', isVIP: true },
{ id: 2, name: '李四', age: 30, avatar: 'avatar2.jpg', isVIP: false },
{ id: 3, name: '王五', age: 28, avatar: 'avatar3.jpg', isVIP: true }
]
}
}
}
</script>
vue
<!-- 子组件:UserCard.vue -->
<template>
<div class="user-card" :class="{ vip: isVip }">
<img :src="avatar" :alt="name">
<h3>{{ name }}</h3>
<p>年龄:{{ age }}岁</p>
<span v-if="isVip" class="vip-badge">VIP</span>
</div>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
},
age: {
type: Number,
required: true,
validator(value) {
return value > 0 && value < 150
}
},
avatar: {
type: String,
default: 'default-avatar.jpg'
},
isVip: {
type: Boolean,
default: false
}
}
}
</script>
<style scoped>
.user-card {
border: 1px solid #ddd;
padding: 20px;
margin: 10px;
}
.user-card.vip {
border-color: gold;
background: #fffef0;
}
.vip-badge {
background: gold;
padding: 2px 8px;
border-radius: 3px;
}
</style>
🎯 第六步:Props的高级技巧
技巧1:批量传递对象属性
vue
<!-- 父组件 -->
<template>
<div>
<!-- ❌ 麻烦的写法 -->
<UserCard
:name="user.name"
:age="user.age"
:email="user.email"
:phone="user.phone"
/>
<!-- ✅ 简洁的写法:v-bind批量传递 -->
<UserCard v-bind="user" />
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: '张三',
age: 25,
email: 'zhangsan@example.com',
phone: '13800138000'
}
}
}
}
</script>
技巧2:Props命名转换
vue
<!-- 父组件:使用kebab-case -->
<template>
<Child
user-name="张三"
user-age="25"
is-vip="true"
/>
</template>
<!-- 子组件:使用camelCase -->
<script>
export default {
props: {
userName: String, // 自动转换
userAge: Number, // 自动转换
isVip: Boolean // 自动转换
}
}
</script>
技巧3:Props默认值的妙用
vue
<script>
export default {
props: {
// 带默认值的按钮
buttonText: {
type: String,
default: '点击我'
},
buttonType: {
type: String,
default: 'primary',
validator(value) {
return ['primary', 'success', 'warning', 'danger'].includes(value)
}
},
// 带默认配置的组件
config: {
type: Object,
default() {
return {
showHeader: true,
showFooter: true,
pageSize: 10
}
}
}
}
}
</script>
⚠️ 重要注意事项
🚫 禁止1:不要修改Props
vue
<script>
export default {
props: ['count'],
methods: {
increment() {
// ❌ 错误!不能直接修改props
this.count++
// ✅ 正确做法1:复制到本地data
// this.localCount++
// ✅ 正确做法2:通知父组件修改
// this.$emit('update:count', this.count + 1)
}
}
}
</script>
🚫 禁止2:对象/数组默认值不用函数
vue
<script>
export default {
props: {
// ❌ 错误!对象默认值必须用函数
user: {
type: Object,
default: { name: '游客' } // 错误!
},
// ✅ 正确!
user: {
type: Object,
default() {
return { name: '游客' }
}
}
}
}
</script>
为什么? 因为对象是引用类型,不用函数的话所有组件实例会共享同一个对象!
🎪 完整实战案例:商品卡片
vue
<!-- 父组件:ProductList.vue -->
<template>
<div class="product-list">
<h1>商品列表</h1>
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
@add-to-cart="handleAddToCart"
/>
</div>
</template>
<script>
import ProductCard from './ProductCard.vue'
export default {
components: { ProductCard },
data() {
return {
products: [
{
id: 1,
name: 'iPhone 15',
price: 5999,
image: 'iphone.jpg',
stock: 10,
tags: ['热销', '新品']
},
{
id: 2,
name: 'MacBook Pro',
price: 12999,
image: 'macbook.jpg',
stock: 5,
tags: ['推荐']
}
]
}
},
methods: {
handleAddToCart(product) {
console.log('添加到购物车:', product.name)
}
}
}
</script>
vue
<!-- 子组件:ProductCard.vue -->
<template>
<div class="product-card">
<img :src="product.image" :alt="product.name">
<h3>{{ product.name }}</h3>
<div class="price">
¥{{ product.price }}
</div>
<div class="tags">
<span
v-for="tag in product.tags"
:key="tag"
class="tag"
>
{{ tag }}
</span>
</div>
<div class="stock">
库存:{{ product.stock }}
</div>
<button
@click="addToCart"
:disabled="product.stock === 0"
>
{{ product.stock > 0 ? '加入购物车' : '已售罄' }}
</button>
</div>
</template>
<script>
export default {
props: {
product: {
type: Object,
required: true,
validator(value) {
// 验证product对象必须有这些属性
return value.id && value.name && value.price !== undefined
}
}
},
methods: {
addToCart() {
// 通知父组件
this.$emit('add-to-cart', this.product)
}
}
}
</script>
<style scoped>
.product-card {
border: 1px solid #ddd;
padding: 20px;
margin: 10px;
border-radius: 8px;
}
.price {
color: #f40;
font-size: 24px;
font-weight: bold;
margin: 10px 0;
}
.tag {
background: #f0f0f0;
padding: 2px 8px;
margin-right: 5px;
border-radius: 3px;
font-size: 12px;
}
button {
width: 100%;
padding: 10px;
background: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
💡 记忆口诀
父传子用Props,冒号绑定动态值
子组件声明接收,类型验证保安全
对象数组用函数,默认值要返回
Props只读不可改,要改通知父组件
🎯 总结
Props就像:
- 🎁 父组件给子组件的礼物
- 📦 单向数据流:只能父→子,不能子→父
- 🔒 只读的:子组件不能修改
- ✅ 要验证:确保数据类型正确
现在明白父组件传递子组件了吗?就是父组件通过Props给子组件传数据,子组件声明接收并使用!😊