写在前面
组件之间的通信,是我们在开发之中很常用的,这篇文章就会带你彻底搞清楚组件之间的几种通信方式,以及使用场景。
方法一:props/$emit
- 首先说props,父组件像子组件传递数据的方法,假设现在有一个名为parent.vue的父组件和一个名为children.vue的子组件,要实现相互之间的通信,就可以采用这种方法,具体看下面实现代码。
<!--父组件 parent.vue-->
<template>
<div id="parent">
<p>我是父组件</p>
<child :msg="msg"></child>
</div>
</template>
//父组件 parent.vue
<script>
import child from "./children";
export default {
data() {
return {
msg: ["hello", "world"],
};
},
components: {
child: child,
},
};
</script>
<!--子组件 children.vue-->
<div id="childern">
<p>我是子组件</p>
<span v-for="item in msg" :key="item"> {{item}} </span>
</div>
//子组件 children.vue
<script>
export default {
props: {
msg: {
type: Array,
}
},
</script>
上面的代码首先父组件parent.vue在data中定义要传递的数据,在子组件上使用v-bind动态进行绑定,接着回到子组件children.vue中,使用props接收父组件传递过来的参数。也就是说父组件通过props向下传递数据。
子组件接收父组件传递来得数据时可以定义接收数据的类型以及默认值,例如:
props: {
msg: {
type: Array, //定义接收参数类型
default:[0,0,0]//定义默认值
}
- $emit是子组件像父组件传递数据的方法,该方法可以通过事件绑定的方式,像父组件传递一个方法或一个值。具体看代码实现
<!-- 子组件 children.vue -->
<template>
<div id="childern">
<p>我是子组件</p>
<button @click="changeTitle">点击像父组件传递数据</button>
</div>
</template>
//子组件 children.vue
<script>
export default {
data() {
return {};
},
methods: {
changeTitle() {
this.$emit("changeTitle", this.title);
},
},
};
</script>
<!--父组件 parent.vue-->
<template>
<div id="parent">
<p>我是父组件</p>
<child @changeTitle="updateTitle"></child>
<button>{{ title }}</button>
</div>
</template>
//父组件 parent.vue
<script>
import child from "./children";
export default {
data() {
return {
title: "传递过来的值",
};
},
components: {
child: child,
},
methods: {
updateTitle(e) {
this.title = e;
},
},
};
</script>
通过上面的代码可以看出子组件children.vue通过**$emit像父组件parent.vue**传递一个事件以及要传递的值,然后回到父组件绑定子组件传递过来的事件,并为他重新定义一个方法接收传递过来的参数并复制到data中的某一项中。
方法二:ref/refs
如果在普通的DOM中绑定ref='domName',使用refs获取DOM,那么指向的就是普通的DOM元素,但在子组件上绑定ref并用refs获取的话,指向的就是该子组件。
<!--父组件 parent.vue-->
<template>
<div id="parent">
<h1>我是父组件</h1>
<child ref="comChild"></child>
</div>
</template>
//父组件 parent.vue
<script>
import child from "./children";
export default {
data() {
return {};
},
components: {
child: child,
},
mounted() {
const comChild = this.$refs.comChild;
console.log(comChild);
comChild.showMsg();
},
};
</script>
<!-- 子组件 children.vue -->
<template>
<div id="childern">
<h2>我是子组件</h2>
</div>
</template>
//子组件 children.vue
<script>
export default {
data() {
return {};
},
methods: {
showMsg() {
console.log("helloWorld");
},
},
};
以上通过ref绑定子组件,通过this.$refs获取到了子组件的实例,并成功访问了子组件的方法。
方法三:eventBus
eventBus,该方法通过一个空的Vue实例作为中央事件总线,可以理解为组件之间的共同桥梁,用它来触发和监听事件,从而实现父子组件、兄弟组件、跨级组件之间的通信。
具体实现方法
const EventBus = new Vue
EventBus.$emit('方法名','传递的数据')
EventBus.$on('方法名',data => {})
- 举个栗子
假设现在又A,B两个兄弟组件,B组件想获取A组件所传递过来的数据,就可以这么实现:
注 在这里我将新建的vue实例单独写在了一个js中
//创建event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
<!-- all.vue -->
<!-- //定义一个.vue文件引入A、B组件-->
<template>
<div id="all">
<com-A></com-A>
<com-B></com-B>
</div>
</template>
<script>
import comA from "../components/A";
import comB from "../components/B";
export default {
data() {
return {};
},
components: {
comA,
comB,
},
};
</script>
<!-- A.vue-->
<template>
<div id="">
<!--绑定传递参数的事件 -->
<button @click="sendMsg()">我是A组件</button>
</div>
</template>
<script>
//使用解构的方式导入EventBus
import { EventBus } from "../utils/event-bus";
export default {
data() {
return {};
},
methods: {
//传递参数的方法
sendMsg() {
//使用EventBus.$emit发送一个事件
EventBus.$emit("aMsg", "来自A页面的消息");
},
},
};
</script>
<!--B.vue -->
<template>
<div id="">
<h3>我是B组件</h3>
</div>
</template>
//使用解构的方式导入EventBus
import {EventBus} from "../utils/event-bus";
export default {
mounted() {
//使用EventBus.$on绑定一个事件,并接收数据
EventBus.$on("aMsg", (msg) => {
console.log(msg);
});
},
};
</script>
结果如下
eventBus也有不方便之处, 当项目较大,就容易造成难以维护的灾难,当项目比较大时建议使用vuex
方法四:Vuex
Vuex是一个集中的状态管理器,在全局中有一个state用来存放数据,要想改变state中的数据必须提交相应mutation进行,mutation是同步的方法,如果异步的请求还要放在action中,通过action来触发mutation,由此完成数据的修改。
Vuex有五个模块:
- state:唯一的数据源与vue实例中我们熟悉的data遵从一样的规则 获取state定义的值$store.state.key名
- mutation:在vuex中更改store中的状态唯一的方法就是提交mutation,这非常类似于时间 使用$store.commit(‘方法名’)触发。
- action:action类似于mutation 但值得注意的是 action是提交mutation 并不是直接变更状态,而且action可以包含任意的异步操作。
- getters:可以认为是 store 的计算属性,就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值。
- module 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块module。
具体的教程可以参考我的另一篇文章~ Vuex 五分钟带你初识Vuex|基础
方法五:provide/inject
该方法是vue2.2.0新增的api,由祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。 不论子组件嵌套有多深,只要通过inject就可以实现注入。
//祖先组件提供变量
export default {
provide: {
name: "注入文本",
},
};
//组件注入
export default {
inject: ["name"],
data() {
return {};
},
mounted() {
console.log(this.name);//注入文本
},
};
方法六: $attrs/$listeners
假设现在有A、B、C三个组件嵌套关系,A组件想向C组件传递数据,这种隔代关系要怎么传递呢
- 首先可以使用props逐级传递
- 或使用eventBus时间总线进行中转,但在开发一般为合作开发,不利于维护
- vuex如果只是简单的数据传递有点大材小用
此时就可以使用Vue为我们提供的$attrs/$listeners
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
举个例子
<!-- all.vue-->
<template>
<div id="all">
<!-- 像A子组件传递数据 -->
<com-A
:first="first"
:second="second"
:third="third"
:fourth="fourth"
></com-A>
</div>
</template>
<script>
import comA from "../components/A";
export default {
data() {
return {
first: "one",
second: "tow",
third: "three",
fourth: "four",
};
},
components: {
comA,
},
};
</script>
<!-- A组件-->
<template>
<div id="comA">
<h3>comA组件</h3>
<p>first:{{first}}</p>
<!-- 通过$attrs获取到all组件中未被prop接收的数据 -->
<p>all的$attrs:{{$attrs}}</p>
<!-- 像B组件传递 -->
<comB v-bind="$attrs"></comB>
</div>
</template>
<script>
import comB from "../components/B";
export default {
props: {
first: {
type: String,
},
},
components: {
comB,
},
};
</script>
<!-- B组件-->
<template>
<div id="comA">
<h3>comA组件</h3>
<p>second:{{second}}</p>
<!-- 通过$attrs获取到A组件中未被prop接收的数据 -->
<p>all的$attrs:{{$attrs}}</p>
<!-- 像C组件传递 -->
<comB v-bind="$attrs"></comB>
</div>
</template>
<script>
import comC from "../components/C";
export default {
props: {
second: {
type: String,
},
},
components: {
comC,
},
};
</script>
<!-- C组件-->
<template>
<div id='comC'>
<h3>comC组件</h3>
<p>third:{{third}}</p>
<!-- 通过$attrs获取到A组件中未被prop接收的数据 -->
<h3>comC的$attrs:{{$attrs}}</h3>
</div>
</template>
<script>
export default {
props: {
third: {
type: String,
},
},
};
</script>
最终结果
attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。
简单来说: listeners 是两个对象, listeners里存放的是父组件中绑定的非原生事件。
方法七:children
此方法通过children来访问他的所有子组件
<!-- parent.vue -->
<template>
<div id="parent">
<children></children>
<button @click="getChildMsg1()">访问子组件方法</button>
<button @click="getChildMsg2()">访问子组件属性</button>
</div>
</template>
<script>
import children from "../components/children";
export default {
data() {
return {
msgP: "我是父组件文本",
};
},
components: {
children,
},
methods: {
parentMsg() {
console.log("我是父组件的方法");
},
getChildMsg1() {
this.$children[0].childrenMsg();//我是子组件方法
},
getChildMsg2() {
console.log(this.$children[0].msgC);//我是子组件文本
},
},
};
</script>
<!-- children -->
<template>
<div id="childern">
<h2>我是子组件</h2>
<button @click="getParentMsg1()">访问父组件的方法</button>
<button @click="getParentMsg2()">访问父组件的属性</button>
</div>
</template>
<script>
export default {
data() {
return {
msgC:'我是子组件的文本'
};
},
methods: {
childrenMsg(){
console.log('我是子组件的方法');
},
getParentMsg1(){
this.$parent.parentMsg()//我是父组件的方法
},
getParentMsg2(){
console.log(this.$parent.msgP); //我是父组件文本
}
},
};
</script>
方法八:localStorage / sessionStorage
这种方法比较简单也就是我们常说的本地储存 可以通过存取数据完成组件之间的通信
window.localStorge.getItem('key')
window.locaoStorge.setItem('key',data)
//注意用JSON.parse() / JSON.stringify() 做数据格式转换
localStorage/sessionStorage可以结合Vuex Vuex存储数据是响应式的无法永久保存 刷新之后会回到初始状态,可以结合本地储存,在刷新后判断本地是否有储存的值,如果有取出来复制到state中。
总结
组件通信大致可以分为三类
- 父子组件通信: props/parent / provide / inject ;refs ; listeners
- 兄弟组件通信: eventBus ; vuex
- 跨级通信: eventBus;Vuex;provide / inject 、listeners
以上就是我总结的组件通信方式的全部内容,如果对你有帮助的话,希望可以收到点赞鸭~