彻底理解Vue组件间通信(6种方式)
在vue的学习中,vue组件间的通信是不得不了解的,在实际开发中,也是非常常用的,所以这里我总结了Vue组件的通信的6种方式,希望能帮助小伙伴们更好更快的去理解Vue组件间的通信
组件关系说明
由上边的图可以看出以下几个关系:
-
A与B是父子关系
-
A与C是父子关系
-
B与D是父子关系
-
C与E是父子关系
-
B与C是兄弟关系
-
D与E是堂兄关系
-
A与D是隔代关系
-
A与E是隔代关系
由上边几个关系对下边场景预览:
- 父子 组件之间的数据传递
- 兄弟 组件之间的数据传递
- 祖先组件 与 子组件 之间的数据传递
下面来为大家详细讲解实现这些关系的几种通信方式
props/$emit
$parent / $children与 ref
$emit/$on
vuex
$attrs/$listeners
provide/inject
复制代码
几种组件通信方法更好地选用
当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
节制地使用$parent和$children,它们的主要目的是作为访问组件的应急方法,
更推荐用props和events实现父子组件通信
如果仅仅是传递数据,就用$attrs/$listeners好点
如何不仅传递数据,还做中间处理,就用vuex好些
复制代码
方法一:props/$emit
- 父组件A 向 子组件B 传递数据 通过props的方法
- 子组件B 向 父组件A 发送数据 通过emit
父组件向子组件传递数据
下面通过一个例子来看父组件是如何向子组件传递数据的: 这个例子是子组件son.vue 通过 props 获取 父组件father.vue 中的数据: sonList: ['小白', '小红', '小蓝','小绿']
- father父组件
<template>
<div class="father">
<com-son :sons="sonList"></com-son>
</div>
</template>
<script>
import comSon from './son'
export default {
name: 'HelloWorld',
components: { comSon },
data() {
return {
sonList: ['小白', '小红', '小蓝','小绿']
}
}
}
</script>
复制代码
- 子组件 son.vue
<template>
<div>
<span v-for="(item, index) in sons" :key="index">{{item}}</span>
</div>
</template>
<script>
export default {
props: ['sons']
}
</script>
复制代码
注意:
props 只可以从上一级组件传递到下一级组件,也就是父子组件,即这就是单向数据流
props是只读,不可以被修改,所有被修改都会失效和被警告
子组件向父组件传递数据
下面通过一个例子来看子组件是如何向父组件传递值:这个例子是子组件son.vue通过$emit向父组件值的传递
- 父组件father.vue
<template>
<div class="father">
<com-son :sons="sonList" @onEmitIndex="onEmitIndex"></com-son>
<p>{{currentIndex}}</p>
</div>
</template>
<script>
import comSon from './son'
export default {
name: 'HelloWorld',
components: { comSon },
data() {
return {
currentIndex: -1,
sonList: ['小白', '小红', '小蓝','小绿']
}
},
methods:{
onEmitIndex(idx){
this.currentIndex = idx
}
}
}
</script>
复制代码
- 子组件son.vue
<template>
<div>
<span v-for="(item, index) in sons" :key="index" @click="emitIndex(index)">{{item}}</span>
</div>
</template>
<script>
export default {
props: ['sons'],
methods: {
emitIndex(index){
this.$emit('onEmitIndex',index)
}
}
}
</script>
复制代码
方法二:children与ref
- 子实例可以用this.$parent访问父实例
- 子实例被推入父实例的$children
- ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
在这里我要说一下:
节制地使用$parent和$children,它们的主要目的是作为访问组件的应急方法,
更推荐用props和events实现父子组件通信
复制代码
下面我们来通过一个实例说明children的用法:
- 父组件father.vue
<template>
<div class="father">
<com-son></com-son>
<button @click="name">点击改变子组件的值</button>
</div>
</template>
<script>
import comSon from './son'
export default {
name: 'HelloWorld',
components: { comSon },
data() {
return {
msg: 'hello,早上好!'
}
},
methods:{
name(){
this.$children[0].message = "hello"
}
}
}
</script>
复制代码
- 子组件son.vue
<template>
<div class="com_a">
<span>{{message}}</span>
<p>获取父组件的值:{{parentVal}}</p>
</div>
</template>
<script>
export default {
data(){
return {
message:'Good wether'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
</script>
复制代码
下边再说ref访问组件的例子:
- 子组件
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
复制代码
- 父组件
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title);
comA.sayHello();
}
}
</script>
复制代码
注意:
这两种方法的弊端,无法在跨域兄弟间通信
on
这个方法可用于父子、隔代、兄弟组件通信
这种方式是通过一个类似App.vue的实例作为一个模块的事件中心,用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件
当项目比较大的时候,可以选择更好的状态管理解决方案vuex
下边我们来通过一个实例说明emit/on的用法:
在这里先说一下:
有两个组件A、B,在B组件中接收到A组件传过来的数据
首先开辟个新的Vue根实例
然后我们在A组件中通过$emit方式去定义一个自定义事件方法
然后通过$on去接收A组件自定义的事件传过来的值
- 首先创建一个vue的空白实例
import Vue from 'vue'
export default new Vue()
复制代码
- 子组件A
把组件A通过$emit传到那个Vue空白实例里面
复制代码
<template>
<div>
<span>A组件->{{msg}}</span>
<input type="button" value="把a组件数据传给b" @click ="send">
</div>
</template>
<script>
import vmson from "util/emptyVue"
export default {
data(){
return {
msg:{
a:'666',
b:'999'
}
}
},
methods:{
send:function(){
vmson.$emit("aevent",this.msg)
}
}
}
</script>
复制代码
- 子组件B
组件B通过$on去监听vmson实例中的自定义方法aevent
复制代码
<template>
<div>
<span>{{msg}}</span>
</div>
</template>
<script>
import vmson from "util/emptyVue"
export default {
data(){
return {
msg:""
}
},
mounted(){
vmson.$on("aevent",(val)=>{//监听aevent事件
console.log(val);//打印出来结果
this.msg = val;
})
}
}
</script>
复制代码
- 父组件
这个父组件就是把A、B两个组件放在父组件中注册渲染
复制代码
<template>
<div>
<childa></childa>
<childb></childb>
</div>
</template>
<script>
import childa from './childa.vue';
import childb from './childb.vue';
export default {
components:{
childa,
childb
},
data(){
return {
msg:""
}
},
methods:{
}
}
</script>
复制代码
Vuex
Vuex是什么?
Vuex是一个专为Vue.js应用程序开发的状态管理模式,它解决了组件之间同一状态的共享问题,它采用集中式存储管理应用的所有组件的状态,所以组件就可以和stort通讯了,其实Vuex就是用来管理组件之间通信的一个组件
为什么要使用Vuex?
假如不使用vuex
-
父子组件依赖同一个state
-
兄弟组件依赖同一个state
用了Vuex之后
Vuex各个模块
state
state中存放页面共享的状态字段
复制代码
getters
相当于当前模块state的计算属性
复制代码
mutations
如果想更新state中的字段,提交mutations中定义的事件的唯一的方式
(key为事件名,value是一个函数),但是中国事件函数必须是同步执行的
复制代码
actions
可以定义异步函数,并在回调中提交mutation,就相当于异步更新了state中的字段
复制代码
modules
类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
复制代码
Vuex实例应用
- 父组件
<template>
<div id="app">
<ChildA/>
<ChildB/>
</div>
</template>
<script>
import ChildA from 'components/ChildA'
import ChildB from 'components/ChildB'
export default {
name: 'App',
components: {ChildA, ChildB}
}
</script>
复制代码
- 子组件ChildA
<template>
<div id="childA">
<h1>我是A组件</h1>
<button @click="transform">点我让B组件接收到数据</button>
<p>{BMessage}}</p>
</div>
</template>
<script>
export default {
data() {
return {
AMessage: 'Hello,B组件,我是A组件'
}
},
computed: {
BMessage() {
return this.$store.state.BMsg
}
},
methods: {
transform() {
this.$store.commit('receiveAMsg', {
AMsg: this.AMessage
})
}
}
}
</script>
复制代码
- 子组件ChildB
<template>
<div id="childB">
<h1>我是B组件</h1>
<button @click="transform">点我看A组件接收的数据</button>
<p>{{AMessage}}</p>
</div>
</template>
<script>
export default {
data() {
return {
BMessage: 'Hello,A组件,我是B组件'
}
},
computed: {
AMessage() {
return this.$store.state.AMsg
}
},
methods: {
transform() {
this.$store.commit('receiveBMsg', {
BMsg: this.BMessage
})
}
}
}
</script>
复制代码
- vuex模块store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const state = {
// 初始化A和B组件的数据,等待获取
AMsg: '',
BMsg: ''
}
const mutations = {
receiveAMsg(state, payload) {
state.AMsg = payload.AMsg
},
receiveBMsg(state, payload) {
state.BMsg = payload.BMsg
}
}
export default new Vuex.Store({
state,
mutations
})
复制代码
listeners
用在父组件传递数据给子组件或者孙组件
如果仅仅是传递数据,就用$attrs/$listeners好点
如何不仅传递数据,还做中间处理,就用vuex好些
复制代码
$attrs:
$attrs继承所有的父组件属性(除了prop传递的属性、class和style)
当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
$listeners:
它是一个对象,包含了父作用域中的v-on事件监听器,可以配合v-on="$listeners"将所有的事件监听器指向这个组件的某个特定的子元素
下面我们来通过一个实例看listeners的用法:
例:假设有三个组件:A组件包含B组件,B组件包含C组件
- A组件
<template>
<div id="app">
<child1 :p-child1="child1" :p-child2="child2" v-on:test1="onTest1"
v-on:test2="onTest2"
//此处监听了两个事件,可以在B组件或者C组件中直接触发
>
</child1>
</div>
</template>
<script>
import Child1 from './Child1.vue';
export default {
data() {
return {};
},
components: { Child1 },
methods: {
onTest1() {
console.log('test1 running...');
},
onTest2() {
console.log('test2 running');
}
}
};
</script>
复制代码
- B组件
<template>
<div class="child-1">
<p>in child1:</p>
<p>props: {{pChild1}}</p>
<p>$attrs: {{$attrs}}</p>
<hr>
<child2 v-bind="$attrs" v-on="$listeners"></child2>
//v-on绑定了$listeners,所以C组件能直接触发test
//v-bind绑定了$attrs,所以C组件可以获取A组件传递下来的props的值
</div>
</template>
<script>
import Child2 from './Child2.vue';
export default {
props: ['pChild1'],
data() {
return {};
},
inheritAttrs: false,
components: { Child2 },
mounted() {
this.$emit('test1');
}
};
</script>
复制代码
- C组件
<template>
<div class="child-2">
<p>in child2:</p>
<p>props: {{pChild2}}</p>
<p>$attrs: {{$attrs}}</p>
<hr>
</div>
</template>
<script>
export default {
props: ['pChild2'],
data() {
return {};
},
inheritAttrs: false,
mounted() {
this.$emit('test2');
}
};
</script>
复制代码
provide/inject
祖先组件中通过provider来提供变量,然后在孙组件中通过inject来注入变量
procide/inject API主要解决了跨域组件间的通讯问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系
下边通过一个例子来说明provide/inject的用法:
- 父组件
<template>
<div>
<son prop="data"></son>
</div>
</template>
<script>
export default {
provide: {
name: 'Tom'
}
}
复制代码
- 孙子组件
这里的孙子组件指的是:父组件、子组件、孙子组件
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: 'grandson',
inject: [name]
}
</script>
复制代码
这里可以通过inject直接访问其两个层次以上的数据,
用法与props完全相同
总结
vue组件间的通讯大致可以分为三类
父子通讯
props/emit、parent/children、 attrs/$listeners、provide/inject API、ref
父向子传递数据通过props
子向父传递是通过$emit、event
子实例访问父实例通过$parent
父实例访问子实例通过$children
$attrs用父组件传递数据给子组件或孙组件
(包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外))
listeners用父组件传递数据给子组件或孙组件
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
祖先组件通过provider提供变量给子孙组件
子孙组件通过inject注入变量给祖先组件
ref用来访问组件实例
复制代码
兄弟通信
Vuex
vuex用来作为兄弟之间和跨级之间的通信
复制代码
跨级通信
Vuex、attrs/listeners、provide/inject API
vuex用来作为兄弟之间和跨级之间的通信
$attrs用父组件传递数据给子组件或孙组件
(包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外))
listeners用父组件传递数据给子组件或孙组件
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器
祖先组件通过provider提供变量给子孙组件
子孙组件通过inject注入变量给祖先组件
复制代码