前言
对于 Vue 的初学者来说,父子组件的通信是经常会使用到的技巧。本篇文章既是近期学习 Vue 的个人总结,也给刚入门 Vue的前端们提供一个参考。
组件是个啥
在一个 Vue 文件中,可以包含很多个组件,就像搭积木一样,由多个小积木搭建出一个完整的应用。小积木我们可以称为子组件,完整的页面我们可以称为父组件。
在下图中,Money.vue
文件由 NumberPad.vue
、Tags.vue
、Tabs.vue
等多个子组件构成。

那么这些子组件是如何把数据传递到父组件Money上的呢?他们两者如何进行交互?这就涉及到本文最关键的数据通信方式了!
爸爸传给孩子:props
想要把父组件的数据传递给子组件,一共有3步:
- 在子组件中声明
props
; - 子组件中定义要传递的数据是什么;
- 在父组件中引入子组件,并声明数据。
子组件 中使用 props
声明一个自定义属性名:
<template>
<div>
child:这是父组件给我传的数据——{{自定义属性名}}
</div>
</template>
<script>
export default {
name:'Childs',
props:['自定义属性名'],
}
</script>
复制代码
父组件中引入子组件,使用v-bind
进行双向绑定,同时声明一个需要展示的 name
即可。
<template>
<div>
parent:下面是我的子组件
<子组件名 :自定义属性名='name'></子组件名>
</div>
</template>
<script>
import 子组件名 from './Childs'
export default {
name:'Parent',
components:{
子组件名
},
data(){
return{
name:'啊哈'
}
}
}
</script>
复制代码
针对props
的值,我们还可以使用装饰器语法(vue component decorator),对props
的值进行简化,并配合TS使用声明类型。装饰器的@Prop
可以添加的参数有数据类型、是否只读和预设值。
子组件
@Component
export default class Child extends Vue {
@Prop(这里填类型,首字母需大写) 自定义属性名: string | undefined;
@Prop({default: '预设值'}) readonly 自定义属性名: string | undefined
}
复制代码
父组件
<template>
<div>
parent:下面是我的子组件
<子组件 :自定义属性名='name'></子组件>
</div>
</template>
复制代码
孩子传给爸爸:this.$emit
子组件给父组件传递数据,不能通过props
实时传输,需要通过this.$emit()
来触发传递事件,通知父组件修改数据。
this.$emit()
第一个参数为事件的 名称 ,第二个参数为传递的数据,是一个可选的参数。父组件必须监听同样的事件名称才能监听到我们的这个事件,事件抛出的值必须通过父组件 $event
或者通过一个方法来访问。
子组件展示一个按钮,点击即可在父组件上实现数字+1
的操作。
<template>
<div>
给老爸传递数据
<button @click="sendMsgtoParent">点击传递</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import {Component, Prop} from 'vue-property-decorator';
@Component
export default class Child extends Vue {
//声明一个名为number1的数据,初始值为0,数据类型number
@Prop({default: 0}) number1!: number
//声明一个sendMsgtoParent方法,调用this.$emit,填写参数名和操作,让数字+1
sendMsgtoParent(number1: number){
this.$emit('getNumber',this.number1 ++)
}
}
</script>
复制代码
父组件中引入子组件,并添加一个子组件那边传递过来的getNumber
,父组件调用getMsg
函数来实现+1
。
<template>
<Child @getNumber="getMsg"/>{{number1}}
</template>
<script lang="ts">
import Child from '@/components/Child.vue';
@Component({
components: {Child}
})
export default class Money extends Vue {
//定义父组件的number1为0,作为数据容器,调用getMsg方法来复制
number1 = 0
getMsg(number1: number){
this.number1 = number1
}
</script>
复制代码
程序运行结果如下:

父子双向绑定语法糖
v-model
在上文中有提到,子组件获取父组件的值要通过props来进行传递,父组件获取子组件数据则要通过调用$emit方法。而v-model能够简化这个步骤,让我们来看一段代码(为方便理解,以伪代码的形式展示):
<子组件 v-model="something"></子组件>
<!--以上写法等价于-->
<子组件 :value="something" @input="something = $event.target.value">
复制代码
一个组件上的 v-model
会默认利用名为 value
的 props
和名为 input
的事件,将这两个分开的语句进行合并简化。本质上就是父组件默认传一个为 value
的值给子组件,同时默认获取 input
事件。
.sync修饰符
比如在子组件中有定义一个函数,使用$emit
传递参数 update:value
和 1000
。
export default {
methods:{
do(){
this.$emit('update:value', 1000)
}
}
}
复制代码
<子组件 @update:value="value => this.xxx(父组件的数据) = value(子组件的数据)">
<!--以上写法等价于-->
<子组件 :value.sync="xxx(相当于this.xxx,父组件那到子组件中的1000)">
复制代码
爷孙隔代传递:this.
emit()
子组件中定义了一个方法,想要把这个方法在爸爸的爸爸(爷爷)中使用,应该如何做比较好呢?
如果是按照之前的思路,我们得在父组件中引入子组件,加入自定义方法,然后在爷爷组件中引入父组件,再加入自定义方法,这样会显得很重复且繁琐。
这时候,我们可以子组件中使用 $parent.$emit
来和爷爷组件进行交互。
首先在子组件 Child.vue
中定义一个 do
函数,使用 this.$parent.$emit
方法定义时间名和数据。
<button @click="useMoney">用钱</button>
<script>
export default {
props:{ money: number },
methods:{
useMoney(){
this.$parent.$emit('useMoney', 100)
}
}
}
</script>
复制代码
在爸爸组件 Father.vue
中,引入 Child.vue
,写好 props
并注册 Child
组件。
<Child :money="money">
<script>
export default {
components:{ Child },
props: { money: number }
}
</script>
复制代码
在爷爷 Gradpa.vue
中,引入 Father.vue
组件,并写入自定义事件useMoney
,用爷爷的money
减去孙子的money
。
<Father @useMoney="useMoney">
<script>
export default {
components:{ Father },
data(){ return { money: 10000 } },
methods:{
useMoney(number){ this.money -= number }
}
}
</script>
复制代码
但如果层级过多,比如传递到祖父级的组件,还需要连续调用多个$parent
,有什么办法可以解决呢?
这时候我们可以封装一个$eventDispatch
方法,实现多层级的穿透。
多层级组件穿透:$eventDispatch
这个方法可以直接在 Vue.prototype
上进行扩展,方便每个组件直接调用。
src/extends
目录下新建一个 index.js
,导出一个 eventExtend
。
import eventExtend from './event.js';
export { eventExtend }
复制代码
在 main.js
中解构导入 eventExtend
,并传入 Vue 构造函数,这样我们就可以在 event.js
中拿到 Vue 实例,并在其原型上扩展方法。
import {eventExtend} from './extends';
eventExtend(Vue);
复制代码
src/extends
目录下新建一个 event.js
文件,导出一个函数,该函数在调用时传入一个 Vue 的构造函数,函数中写好 $eventDispatch
方法。
export default function(Vue){
//Vue原型上扩展方法
Vue.prototype.$eventDispatch = function(name, value){
let parent = this.$parent
//开启循环,确认parent是否存在,如过存在则一层一层进行传递,直到不存在位置
while(parent){
parent.$emit(name, value)
parent = parent.$parent
}
}
}
复制代码
封装好 $eventDispatch
方法以后,就可以直接在子组件中进行使用了。
总结
在最后简单总结一下父子组件的通信方式吧!
父组件要传递数据给子组件,一共有3步:
- 在子组件中声明
props
,并定义数据类型; - 子组件中定义要传递的数据是什么;
- 在父组件中引入子组件,并声明数据。
子组件要传递数据给父组件,一共也有3步:
- 子组件中声明一个函数,并调用
$emit
方法,$emit
的第一个参数是自定义属性名,第二个参数是操作; - 父组件中引入子组件;
- 父组件添加一个子组件那边传递过来的操作,更新视图。
v-model
和 .sync
的是父子组件通信常用的语法糖,
v-model
等价于 :value="something" @input="something = $event.target.value"
;
:value.sync
等价于 @update:value="value=>this.xxx=value"
。
使用this.$parent.$emit()
可以实现组件之间的隔代传递。
如果想实现多层级的组件穿透,可以封装 $eventDispatch
。