单文件组件的组件传值_史上最全!vue的7种组件通信方式

51a19a7ea66d02ee359c02fc77461aaf.gif

点击前端充电营,关注我们

fa6564dec4ab2bf39ae849b27e0b130e.gif

前言

今天我们来聊聊Vue的组件通信。

相信使用过Vue的一些小伙伴很容易脑海中总结出Vue常用的几种通信方式

  • Props + $emit

  • EventBus

  • Vuex

其实除了这几种方式,还有另外一些通信方式也很实用,下面来看下吧。

组件通信类别

首先,要学习组件的通信方式,我们得先了解Vue究竟有多少种通信种类。

基本上通信类别可以分成下面几类:

  • 父子组件通信

  • 兄弟组件通信

  • 隔代组件通信

父子通信方式

  1. props + $emit

现在我们先创建一个父组件Parent.vue和一个子组件Son.vue

// Parent.vue<template>  <div class="parent">    <div class="title">我是父组件div>  div>template><script>export default {  name: 'Parent'}script>// Son.vue<template>  <div class="son">    <div class="title">我是子组件div>  div>template><script>export default {  name: 'Son'}script>

然后修改一下子组件,增加一个props属性,并且接收父组件传来的msg字段。

// Son.vue<template>  <div class="son">    <div class="title">我是子组件,我爸爸说:<span class="parent-msg">{{msg}}span>div>  div>template><script>export default {  name: 'Son',  props: {    msg: String  }}script><style lang='less' scoped>.son {  .parent-msg {    color: red;  }}style>

然后修改父组件,引入Son.vue,并且添加属性msg:

<template>  <div class="parent">    <div class="title">我是父组件div>    <Son msg="叫爸爸。" />  div>template><script>import Son from './Son';export default {  name: 'Parent',  components: {    Son  }}script>

最后看到页面上渲染如下:

efddb2ed8787810401eb7447e8b70694.png

上面是父组件传值到子组件的过程,如果子组件想要反过来和父组件通信要怎么做呢?

因为Vue不允许子组件直接修改Props属性,因为这样会导致数据的变化不可预测,父组件难以查找是哪个子组件修改了自己的属性值。

所以Vue提供了一种方式:事件。

子组件可以通过触发一个事件,父组件监听这个事件,然后由父组件来修改自己的props值(或者做其他的操作)。

下面修改一下Son.vue,增加在mounted的时候$emit:

<template>  <div class="son">    <div class="title">我是子组件,我爸爸说:<span class="parent-msg">{{msg}}span>div>  div>template><script>export default {  name: 'Son',  props: {    msg: String  },  mounted() {    this.$emit('onSay', '爸爸');  }}script><style lang='less' scoped>.son {  .parent-msg {    color: red;  }}style>

父组件需要修改一下来监听onSay事件:

<template>  <div class="parent">    <div class="title">我是父组件div>    <Son msg="叫爸爸。" @onSay="handleSay" />  div>template><script>import Son from './Son';export default {  name: 'Parent',  components: {    Son  },  methods: {    handleSay(content) {      console.log(content)    }  }}script>

最后输出如下:

832cf96858a16e507a3e1ea7a497a5f4.png

2. ref / $parent & $children

ref如果绑定在普通dom节点,则指向的就是该dom元素;如果绑定在Vue组件上,指向的就是该组件实例;

$parent和$children分别表示父组件和子组件的实例,因此也可以通过这个方式访问父子组件中的属性和方法;

下面我们来试下这两种写法:

// Parent.vue<template>  <div class="parent">    <div class="title">我是父组件div>    <Son ref="sonRef" />  div>template><script>import Son from './Son';export default {  name: 'Parent',  components: {    Son  },  mounted() {    // 这里调用子组件方法    this.$refs.sonRef.say(); // 爸爸!  }}script>// Son.vue<template>  <div class="son">    <div class="title">我是子组件div>  div>template><script>export default {  name: 'Son',  methods: {    say() {      console.log('爸爸!');    }  }}script>

上面可以看到在父组件中通过this.$refs.sonRef的方式拿到了子组件的Vue实例,并且成功调用了子组件的方法;

如果使用$parent和$children的话,则是下面写法:

// Parent.vue<template>  <div class="parent">    <div class="title">我是父组件,儿子的msg是:{{$children[0].msg}}div>    <Son />  div>template><script>import Son from './Son';export default {  name: 'Parent',  data() {    return {      msg: '叫爸爸'    }  },  components: {    Son  }}script>// Son.vue<template>  <div class="son">    <div class="title">我是子组件,爸爸的msg是:{{$parent.msg}}div>  div>template><script>export default {  name: 'Son',  data() {    return {      msg: '我是儿子'    }  },}script>

看下页面输出:

b8aae3aee27f6c2c2dfe4d2f94a23afa.png

注意上面的代码中,$children是一个数组,所以使用$children[0]才能拿到第一个子组件Son;

兄弟通信方式

接下来介绍兄弟组件之间的通信,兄弟通信不像父子组件一样有那么多的方式;

我们将增加一个Son2.vue组件,直接编写下面代码:

// Son2.vue<template>  <div class="son">    <div class="title">我是子组件二div>  div>template><script>export default {  name: 'Son2',}script>

然后在父组件中增加Son2子组件

// Parent.vue<template>  <div class="parent">    <div class="title">我是父组件div>    <Son />    <Son2 />  div>template><script>import Son from './Son';import Son2 from './Son2';export default {  name: 'Parent',  components: {    Son,    Son2  }}script>

1. EventBus

EventBus不仅仅是兄弟组件通信的方式,实际上父子通信、隔代通信都可以使用;

EventBus的概念就是在每个组件中引入一个空的Vue实例,然后在其中一个组件通过$emit发出事件,然后在另一个组件中通过$on监听该事件,以达到通信的目的;

首先创建这个bus,新建一个bus.js文件

// lib/bus.jsimport Vue from 'vue';export default new Vue();


然后我们修改一下Son和Son2的代码,这两个组件都引入bus.js的Vue实例:

// Son.vue<template>  <div class="son">    <div class="title">我是子组件一,子组件二说:{{son2Msg}}div>  div>template><script>import Bus from '@/lib/bus.js';export default {  name: 'Son',  data() {    return {      son2Msg: ''    }  },  mounted() {    Bus.$on('onSon', msg => {      this.son2Msg = msg;    })  }}script>// Son2.vue<template>  <div class="son">    <div class="title">我是子组件二div>  div>template><script>import Bus from '@/lib/bus.js';export default {  name: 'Son2',  mounted() {    Bus.$emit('onSon', 'hello!我是Son2');  }}script>

看下实际的效果:

0939787ee1400849ce625fd617e84083.png

可以看到跨组件通信就是这么简单!

隔代组件通信

接下来介绍第三种通信类别:隔代组件通信;

正如前面提到的,隔代组件同样可以使用上面的EventBus方式进行通信,下面再介绍两种隔代通信的方式;

1. $attrs/$listeners

第一种是通过$attr和$listeners属性,这个是Vue2.4之后引入的属性,目的就是解决隔代组件通信的问题;

首先我们创建一个孙子组件GrandSon.vue,如下:

// GrandSon.vue<template>  <div class="grand-son">    <div class="title">我是孙组件,爸爸组件说:{{parentMsg2}}, {{parentMsg3}}div>  div>template><script>export default {  name: 'GrandSon',  props: [ 'parentMsg2', 'parentMsg3' ]}script>

上面接收一个props:parentMsg2和parentMsg3,这些属性从哪里来呢?显然,这是从最外层的父组件Parent.vue传进来的;

但是,按照上面我们所学的,props属性只能接收到上一层,也就是Son.vue组件的值,没办法接收到最外层的Parent.vue组件的值;

这时候就需要使用$attrs属性了,修改Son.vue组件,增加如下代码:

// Son.vue<template>  <div class="son">    <div class="title">我是子组件一,爸爸说:{{parentMsg}}div>    <grand-son v-bind="$attrs"  />  div>template><script>import GrandSon from './GrandSon';export default {  name: 'Son',  props: [ 'parentMsg' ],  components: {    GrandSon  }}script>

这时候,父组件Parent.vue传进子组件的props,当子组件接收了其中一部分(上面代码中的parentMsg)之后,剩下的属性就会传递到GrandSon.vue中;

所以我们编写Parent.vue,然后看下效果:

// parent.vue<template>  <div class="parent">    <div class="title">我是父组件div>    <Son parentMsg="爸爸消息1" parentMsg2="爸爸消息2" parentMsg3="爸爸消息3" />  div>template><script>import Son from './Son';export default {  name: 'Parent',  components: {    Son  }}script>

最后输出效果如下:

50fd064ddf9a172a893621c926eb4496.png

可以看到,子组件拿到了消息1,而剩下的消息2和消息3则传递到了孙组件中;

上面介绍了$attrs的用法,那么$listeners又是什么作用呢?

我们知道从父到孙组件是通过$attrs,那么反过来,孙组件想要跟父组件通信需要怎么做呢?这时候就需要$listeners属性了,同样这个属性是放在子组件上:

// Son.vuebind=

然后修改一下孙组件,触发事件返回数据:

// GrandSon.vuemounted() {  this.$emit('onGrandSon', '我是孙组件');}

然后在父组件监听这个事件:

// parant.vue"爸爸消息1" parentMsg2=// ...methods: {  handleGrandSon(val) {    console.log(val);  } }

来看看界面控制台打印:

ad0f9b12cf9f21f968ee84cd11def4a5.png

父组件成功拿到孙组件的数据;

2. provide/inject 

这是另一种处理隔代组件通信的方式,但是官网不推荐使用,理由是:你管不好;

因为这是依赖注入的,所以可能比较难以追踪到底哪里依赖了provide的数据,状态追踪比较困难;

简单说一下用法,首先修改parent.vue,编写provider:

// parent.vue<template>  <div class="parent">    <div class="title">我是父组件div>    <Son />  div>template><script>import Son from './Son';export default {  name: 'Parent',  provide: {    parentMsg: '爸爸消息'  },  components: {    Son  }}script>

修改孙组件,使用inject注入父组件的数据:

// GrandSon.vue<template>  <div class="grand-son">    <div class="title">我是孙组件,爸爸组件说:{{parentMsg}}div>  div>template><script>export default {  name: 'GrandSon',  inject: [ 'parentMsg' ]}script>

可以看到孙组件成功拿到了parentMsg:

c9698d220c7f5b6f9552c7380a285943.png

这个方式也只能是孙组件获取父组件数据,而没有提供孙组件通知父组件的方式;

Vuex

接下来介绍最后一种万能解决方法:Vuex,它能解决三种通信方式;

唯一的缺点就是,如果你的应用不复杂,就没必要引入Vuex,同时它也有一定的学习成本和维护成本;

我们这篇文章就简单介绍下使用的方式:

首先是安装Vuex:

npm install --save-dev vuex

然后在src下创建store目录,store目录下创建index.js文件;

fc270c1e600182b91179f3ed7f9e013a.png

在main.js中引入store:

// main.jsimport Vue from 'vue'import App from './App.vue'import store from './store/index'Vue.config.productionTip = falsenew Vue({  store,  render: h => h(App),}).$mount('#app')

接下来编写store/index.js文件,这里需要我们事前对vuex有一定了解;

它主要有以下几个基本概念:

  • Store: 一个js对象,里面保存了应用所有的状态数据,一个应用也只会有一个store;

  • Getter: 可以理解为计算属性,用于在获取store数据之前做一些数据处理;

  • Mutation: 修改Store的唯一方式,不支持异步操作,因为异步操作会使得应用的状态不可预测;

  • Action: 页面端使用dispatch方法触发一个action,action用于触发一个mutation,action里面是支持异步操作的;

  • Module: 模块化编程需要使用这个属性,用于引入多个store的文件;

编写store/index.js文件:

// store/index.jsimport Vue from 'vue'import Vuex from 'vuex'Vue.use(Vuex)export default new Vuex.Store({  state: {    count: 0  },  getters: {    getCount: (state) => {      return state.count    }  },  actions: {    increment({ commit, state }) {      commit('setCount', state.count + 1)    }  },  mutations: {    setCount (state, count) {      state.count = count    }  }})

这里只定义了一个count,用于测试页面变更;

接下来更新parent.vue组件,引入该数据

// parent.vue<template>  <div class="parent">    <div class="title">我是父组件,目前Count: {{getCount}}div>    <Son />  div>template><script>import Son from './Son';import { mapGetters } from 'vuex'export default {  name: 'Parent',  computed: {    ...mapGetters([ 'getCount' ])  },  components: {    Son  }}script>

父组件我们用于展示count的值,这里用的是mapGetters方法,这个方法会将getCount变成一个计算属性,我们可以直接使用;

接着修改Son.vue,给一个点击改变count的按钮:

// son.vue  <div class="son">    <div class="title">我是子组件一div>    <button @click="$store.dispatch('increment')">点击count+1button>  div></template>export default {  name: 'Son',}script>

注意上面代码我们使用this.$store.dispatch触发了store的increment事件,用于更改count;

这时候界面如图所示:

6dfa9f057fdd2b4abbafce91b3506382.png

当我们点击按钮的时候,count也会对应的变更:

17747119929fafe9baccef671beab3f5.png

Vuex我们就介绍到这里,上面我们介绍了7种组件之间的通信方法:

  • props + $emit,适用于父子通信;

  • ref,适用于父子通信;

  • $parent + $children,适用于父子通信;

  • EventBus,适用于父子、兄弟、隔代通信;

  • $attrs + $listeners,适用于隔代通信;

  • provide + inject,适用于隔代通信

  • Vuex,适用于父子、兄弟、隔代通信;

至于什么时候该选择哪一种通信方式,需要结合我们的项目来具体分析;以上就是我所总结的最全Vue组件通信方式;

感谢你的阅读,我们下一篇再见!

5cb4c5de89ebbea1283e57e5f7ce9f80.png

1e9d340c2a400d5b75d94bb0643a9fa0.png

关注【前端充电营】

回复【电子书】

获取100+

技术书籍!

往期推荐

vscode常用的9个插件,推荐给你们

webpack学习教程(一): 基础概念

前端也能轻松年薪20w+?超详细前端入门攻略拿去!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值