简单理解 Vue 父子组件通信

前言

对于 Vue 的初学者来说,父子组件的通信是经常会使用到的技巧。本篇文章既是近期学习 Vue 的个人总结,也给刚入门 Vue的前端们提供一个参考。

组件是个啥

在一个 Vue 文件中,可以包含很多个组件,就像搭积木一样,由多个小积木搭建出一个完整的应用。小积木我们可以称为子组件,完整的页面我们可以称为父组件。

在下图中,Money.vue 文件由 NumberPad.vueTags.vueTabs.vue 等多个子组件构成。

那么这些子组件是如何把数据传递到父组件Money上的呢?他们两者如何进行交互?这就涉及到本文最关键的数据通信方式了!

爸爸传给孩子:props

想要把父组件的数据传递给子组件,一共有3步:

  1. 在子组件中声明 props
  2. 子组件中定义要传递的数据是什么;
  3. 在父组件中引入子组件,并声明数据。

子组件 中使用 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可以添加的参数有数据类型、是否只读和预设值。

参考:Vue Component Decorator文档

子组件

@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 会默认利用名为 valueprops 和名为 input 的事件,将这两个分开的语句进行合并简化。本质上就是父组件默认传一个为 value 的值给子组件,同时默认获取 input 事件。

.sync修饰符

比如在子组件中有定义一个函数,使用$emit传递参数 update:value1000

export default {
  methods:{
    do(){
      this.$emit('update:value', 1000)
    }
  }
}
复制代码
<子组件 @update:value="value => this.xxx(父组件的数据) = value(子组件的数据)">

  <!--以上写法等价于-->
  
<子组件 :value.sync="xxx(相当于this.xxx,父组件那到子组件中的1000)">
复制代码

爷孙隔代传递:this.parent.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步:

  1. 在子组件中声明 props,并定义数据类型;
  2. 子组件中定义要传递的数据是什么;
  3. 在父组件中引入子组件,并声明数据。

子组件要传递数据给父组件,一共也有3步:

  1. 子组件中声明一个函数,并调用 $emit方法,$emit的第一个参数是自定义属性名,第二个参数是操作;
  2. 父组件中引入子组件;
  3. 父组件添加一个子组件那边传递过来的操作,更新视图。

v-model.sync 的是父子组件通信常用的语法糖,
v-model等价于 :value="something" @input="something = $event.target.value"
:value.sync等价于 @update:value="value=>this.xxx=value"

使用this.$parent.$emit() 可以实现组件之间的隔代传递。

如果想实现多层级的组件穿透,可以封装 $eventDispatch

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享