![51a19a7ea66d02ee359c02fc77461aaf.gif](https://i-blog.csdnimg.cn/blog_migrate/bbcee10e0e9f3d499f82779e2f5dee41.gif)
点击前端充电营,关注我们
![fa6564dec4ab2bf39ae849b27e0b130e.gif](https://i-blog.csdnimg.cn/blog_migrate/3a86268ce0408da0a6b0f0a8f8a31389.gif)
前言
今天我们来聊聊Vue的组件通信。
相信使用过Vue的一些小伙伴很容易脑海中总结出Vue常用的几种通信方式
Props + $emit
EventBus
Vuex
其实除了这几种方式,还有另外一些通信方式也很实用,下面来看下吧。
组件通信类别
首先,要学习组件的通信方式,我们得先了解Vue究竟有多少种通信种类。
基本上通信类别可以分成下面几类:
父子组件通信
兄弟组件通信
隔代组件通信
父子通信方式
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>
最后看到页面上渲染如下:
上面是父组件传值到子组件的过程,如果子组件想要反过来和父组件通信要怎么做呢?
因为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>
最后输出如下:
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>
看下页面输出:
注意上面的代码中,$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>
看下实际的效果:
可以看到跨组件通信就是这么简单!
隔代组件通信
接下来介绍第三种通信类别:隔代组件通信;
正如前面提到的,隔代组件同样可以使用上面的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>
最后输出效果如下:
可以看到,子组件拿到了消息1,而剩下的消息2和消息3则传递到了孙组件中;
上面介绍了$attrs的用法,那么$listeners又是什么作用呢?
我们知道从父到孙组件是通过$attrs,那么反过来,孙组件想要跟父组件通信需要怎么做呢?这时候就需要$listeners属性了,同样这个属性是放在子组件上:
// Son.vuebind=
然后修改一下孙组件,触发事件返回数据:
// GrandSon.vuemounted() { this.$emit('onGrandSon', '我是孙组件');}
然后在父组件监听这个事件:
// parant.vue"爸爸消息1" parentMsg2=// ...methods: { handleGrandSon(val) { console.log(val); } }
来看看界面控制台打印:
父组件成功拿到孙组件的数据;
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:
这个方式也只能是孙组件获取父组件数据,而没有提供孙组件通知父组件的方式;
Vuex
接下来介绍最后一种万能解决方法:Vuex,它能解决三种通信方式;
唯一的缺点就是,如果你的应用不复杂,就没必要引入Vuex,同时它也有一定的学习成本和维护成本;
我们这篇文章就简单介绍下使用的方式:
首先是安装Vuex:
npm install --save-dev vuex
然后在src下创建store目录,store目录下创建index.js文件;
在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;
这时候界面如图所示:
当我们点击按钮的时候,count也会对应的变更:
Vuex我们就介绍到这里,上面我们介绍了7种组件之间的通信方法:
props + $emit,适用于父子通信;
ref,适用于父子通信;
$parent + $children,适用于父子通信;
EventBus,适用于父子、兄弟、隔代通信;
$attrs + $listeners,适用于隔代通信;
provide + inject,适用于隔代通信
Vuex,适用于父子、兄弟、隔代通信;
至于什么时候该选择哪一种通信方式,需要结合我们的项目来具体分析;以上就是我所总结的最全Vue组件通信方式;
感谢你的阅读,我们下一篇再见!
![1e9d340c2a400d5b75d94bb0643a9fa0.png](https://i-blog.csdnimg.cn/blog_migrate/1e3056d1aada858cdaf7b291bb9d95cc.jpeg)
关注【前端充电营】
回复【电子书】
获取100+
技术书籍!
往期推荐
vscode常用的9个插件,推荐给你们
webpack学习教程(一): 基础概念
前端也能轻松年薪20w+?超详细前端入门攻略拿去!