在前几篇文章中,我们学习了组件化开发的基础知识。今天我们来重点攻克Vue组件开发中最重要的内容------组件间的数据通信。本文将详细介绍最常用的Props传值方式,涵盖父传子、子传父以及跨级传递三种场景。
一、组件通信概述
在Vue应用中,组件是独立的存在。为了让组件之间能够协同工作,我们需要一套完善的数据通信机制。
常见的组件关系:
- 父子关系:最常见的组件关系
- 兄弟关系:同属于一个父组件的两个子组件
- 跨级关系:祖孙组件之间的通信
本文重点 :使用 Props 实现父子组件和跨级组件的通信。
二、父组件向子组件传递数据(Props)
2.1 核心步骤
父组件向子组件传值分为两步:
- 父组件中 :在子组件的标签上使用自定义属性(
v-bind)传递数据 - 子组件中 :使用
props选项声明接收父组件传递过来的数据
2.2 代码示例
父组件(App.vue):
<template>
<div id="app">
<!-- 1. 在子组件标签上绑定数据 -->
<girl :abc="123" :money1="money" :a="a" />
<son :car="car" />
</div>
</template>
<script>
import girl from './components/girl.vue';
import son from './components/son.vue';
export default {
name: 'App',
data() {
return {
money: 10000,
car: '宝马',
a: { b: 666 }
};
},
components: {
girl,
son
}
};
</script>
子组件(girl.vue):
<template>
<div class="girl">
<h2>girlMsg: {{ girMsg }}</h2>
<!-- 3. 在模板中像使用data一样使用props -->
<h3>来自父亲的money: {{ money1 }}</h3>
<h3>来自父亲的abc: {{ abc }}</h3>
<h3>来自父组件的b: {{ a.b }}</h3>
</div>
</template>
<script>
export default {
name: 'girl',
data() {
return {
girMsg: 'girl组件'
};
},
// 2. 使用props接收数据
props: ['abc', 'money1', 'a'] // 数组形式(简单声明)
};
</script>
2.3 Props的三种声明方式
方式一:数组形式(简单声明)
props: ['abc', 'money1', 'a']
特点:只声明接收哪些属性,不做类型校验。
方式二:对象形式(类型验证)
props: {
abc: String,
money1: Number
}
特点:指定每个属性的类型,类型不匹配时会在控制台发出警告。
方式三:对象的对象形式(完整验证)
props: {
abc: {
type: String, // 类型
required: true, // 是否必须传递
default: '默认值' // 默认值
},
money1: {
type: Number,
default: 99 // 默认值
}
}
完整验证配置项:
| 配置项 | 作用 | 说明 |
|---|---|---|
type |
指定属性的类型 | String, Number, Boolean, Array, Object, Function |
required |
是否必须传递 | true/false |
default |
默认值 | 未传递时使用该值 |
validator |
自定义验证函数 | 返回true表示验证通过 |
自定义验证示例:
props: {
age: {
type: Number,
validator(value) {
return value >= 0 && value <= 150;
}
}
}
2.4 重要注意事项
⚠️ 单向数据流
子组件不应直接修改props。修改会触发警告,且父组件的数据不会更新。
// ❌ 错误:直接修改props
this.money1 = 20000; // 会报警告
为什么要有单向数据流?
- 防止子组件意外修改父组件的状态
- 保证数据流向清晰,便于调试和维护
⚠️ 复杂数据类型(对象、数组)
修改内部属性 :Vue可以检测到,但不推荐这样做,因为它破坏了数据流的清晰性。
// 可以修改,但不推荐
this.a.b = 999; // Vue能检测到变化
直接替换整个对象:Vue检测不到,且会报错。
// ❌ 错误:直接替换整个对象
this.a = { b: 888 }; // Vue检测不到,会报错
正确做法:如果需要修改,应该在父组件中进行。
三、子组件向父组件传递数据(自定义事件)
3.1 核心步骤
子组件向父组件传值分为三步:
- 父组件中:定义一个方法,用于接收数据和处理逻辑
- 父组件中 :将这个方法通过props传递给子组件
- 子组件中:通过props接收父组件传过来的函数,在需要传递数据时调用该函数并将数据作为参数传入
3.2 代码示例
父组件(App.vue):
<template>
<div id="app">
<h1>app组件</h1>
<h3>来自girl组件的孝敬:{{ girlMoney }}</h3>
<div class="box">
<!-- 2. 将方法传递给子组件 -->
<girl :getGirlMoney="getGirlMoney" />
</div>
</div>
</template>
<script>
import girl from './components/girl.vue';
export default {
name: 'App',
data() {
return {
girlMoney: 0
};
},
components: { girl },
methods: {
// 1. 父组件定义方法,val形参用于接收子组件数据
getGirlMoney(val) {
this.girlMoney = val; // 3. 用子组件传来的数据更新自身状态
}
}
};
</script>
子组件(girl.vue):
<template>
<div class="girl">
<h2>girlMsg: {{ girlMsg }}</h2>
<h3>girlMoney: {{ girlMoney }}</h3>
<!-- 4. 触发事件:调用父组件传过来的函数,并传递数据 -->
<button @click="getGirlMoney(girlMoney)">点击传递grilMoney</button>
</div>
</template>
<script>
export default {
name: 'girl',
data() {
return {
girMsg: 'girl组件',
girlMoney: 500
};
},
// 3. 子组件接收父组件传递的函数
props: ['getGirlMoney']
};
</script>
3.3 执行流程
用户点击按钮
↓
调用 getGirlMoney(girlMoney)
↓
getGirlMoney 是父组件传过来的函数
↓
执行父组件中的 getGirlMoney(val) 方法
↓
val = 500(子组件的 girlMoney)
↓
this.girlMoney = 500(更新父组件数据)
↓
页面更新显示:来自girl组件的孝敬:500
四、跨级组件通信(Props逐级传递)
4.1 概念
如果爷爷组件(App)需要向孙组件(child)传递数据,而中间隔着一个父组件(son),可以通过props逐级传递。
App(爷爷) → son(父亲) → child(孙子)
4.2 代码示例
爷爷组件(App.vue):
<template>
<div id="app">
<!-- 将car传递给son组件 -->
<son :car="car" />
</div>
</template>
<script>
import son from './components/son.vue';
export default {
name: 'App',
data() {
return {
car: '宝马'
};
},
components: { son }
};
</script>
父组件(son.vue):
<template>
<div class="son">
<h2>sonMsg: {{ sonMsg }}</h2>
<h3>来自父亲的car: {{ car }}</h3>
<!-- 接收App的car,并继续向下传递给child组件 -->
<child :car="car" />
</div>
</template>
<script>
import child from './child.vue';
export default {
name: 'son',
props: ['car'], // 接收来自App的数据
components: { child }
};
</script>
孙组件(child.vue):
<template>
<div class="child">
<h3>childMsg: {{ childMsg }}</h3>
<!-- 最终使用从son传递过来的car -->
<h4>来自爷爷的car: {{ car }}</h4>
</div>
</template>
<script>
export default {
name: 'child',
props: ['car'] // 接收来自son的数据
};
</script>
4.3 逐级传递的局限性
| 问题 | 说明 |
|---|---|
| 繁琐 | 如果组件层级很深,逐级传递会非常麻烦 |
| 冗余 | 中间组件即使不使用该数据,也需要接收并转发 |
| 难维护 | 修改数据结构时需要改动所有中间组件 |
解决方案:
- 简单的跨级传递:可以使用
provide / inject - 复杂的跨级或兄弟组件通信:使用 Vuex 状态管理库
这些内容将在后续文章中详细讲解。
五、总结
5.1 通信方式对比
| 通信方式 | 方向 | 核心API | 适用场景 |
|---|---|---|---|
| Props | 父→子 | v-bind + props |
最常见的数据传递方式 |
| 自定义事件 | 子→父 | 父传函数,子调用 | 子组件通知父组件状态变化 |
| 逐级传递 | 祖先→后代 | 多层props | 简单的跨级传递,层级不深时 |
| provide/inject | 祖先→后代 | provide + inject |
深层级的跨级传递 |
| Vuex | 任意 | 状态管理 | 复杂的跨级或兄弟组件通信 |
5.2 Props最佳实践
| 场景 | 推荐做法 |
|---|---|
| 简单传值 | 数组形式声明props |
| 需要类型校验 | 对象形式声明props |
| 需要默认值或必填 | 完整验证形式 |
| 修改props | ❌ 不要直接修改,通过父组件修改 |
| 复杂类型内部属性 | 可以修改但不推荐 |
| 子传父 | 使用函数类型的props |
5.3 注意事项清单
- ✅ Props遵循单向数据流原则
- ✅ 子组件不要直接修改props
- ✅ 复杂类型(对象/数组)谨慎操作
- ✅ 使用类型验证增强代码健壮性
- ✅ 跨级传递过多时考虑其他方案
掌握了Props传值,你已经可以应对大部分组件通信场景。下一篇文章我们将继续学习 $emit 事件派发和更高级的通信方式,敬请期待!