vue实现组件间通信的方法
1. props + $emit
这是我们最常用到的方法,props是单向数据流,通过父组件向子组件传递数据可以使用props,但是如果子组件子元素向父元素传参的话,则需要this.$emit(‘methodName’,‘data’)的方式向父组件抛出事件,在父组件中使用v-on进行接收参数
// father.vue
<template>
<div>
<Children :name="name" @close="closeChildren"><Children />
</div>
</template>
<script>
import Children from './children.vue'
export default {
data(){
return {
name: 'vue组件通信总结'
}
},
components: {
Children
},
methods: {
closeChildren() {
// todo
}
}
};
</script>
// children.vue
<template>
<div>
<h3>{{name}}</h3>
<el-button
v-if="closeBtn"
circle
size="mini"
class="close_btn"
icon="el-icon-close"
@click="close"
></el-button>
</div>
</template>
<script>
export default {
props: ["name"],
methods: {
close() {
this.$emit("close");
}
}
};
</script>
如果我们不能通过标签的形式来实现子组件,比如我们需要动态插入子组件时,就需要暴露一个接口,可以将要传入的父组件的this传入,然后向这个this进行$emit事件的抛出
emitParent:function(parent){
this.$emit.call(parent,'close','data');
}
2. 父子间通信还可以用parent 和 children,以及指定ref
$parent用于访问父组件,父组件是唯一的
this.$parent.name; // 访问父组件的name
$children用于访问子组件,子组件可能有一个或者多个,所以返回值是数组
this.$children.forEach((VueComponent)=>{
if(VueComponent.name === "NeedComponent"){
console.log("success");
}
})
$children的缺点是无法确定子组件的访问顺序,如果确定知道需要访问的组件时,可以用ref的方式
// father.vue
<template>
<div>
<Children ref="child1"><Children />
</div>
</template>
<script>
import Children from './children.vue'
export default {
data(){
return {
name: '我是父组件name'
}
},
mounted(){
console.log(this.$refs.child1.name) // ==>我是父组件name123
}
};
</script>
// children.vue
<script>
export default {
data(){
return {
name: '我是子组件name'
}
},
mounted(){
this.name = this.$parent.name + '123';
}
};
</script>
3.利用$route的params或者query
跨路由的组件间通信可以用,但同一个路由下就不适合用了。
this.$router('path',data);
4.Vuex 和 localstorage
vuex相当于全局变量,可以通过 this.$store 的方式在各个组件中进行访问,多用于在多个兄弟组件进行通信或者父子关系超过三层的组件中进行使用
localstroage也是全局变量,不同的是他储存在本地,并且可以通过set get直接获取修改,不需要暴露方法
5.$ root 通信
this.$root是app.vue的祖先,定位根实例属性,可以用于父子组件、兄弟组件通信,
适合通用化(就是不用动态更新的意思)的场景,如username/userId,如果想建立随着改变动态更新的数据,建议使用vuex
但是可以通过传入对象的方式,来进行更改
// children1.vue
<script>
export default {
data(){
return {
msg: "hello"
}
},
mounted(){
this.$root.msg = this.msg
}
};
</script>
// children2.vue
<script>
export default {
mounted(){
console.log(this.$root.msg) // => 'hello'
}
};
</script>
传入对象
// father.vue
<script>
import Child1 from "@/components/children";
export default {
data() {
return {
name: "123",
age: 23,
say: "hello"
};
},
beforeMount() {
this.$root.testMeg = 'mmdjj';
},
components: {
Child1
}
};
</script>
// Child1.vue
<template>
<div>
{{$root.testMsg.name}} // 一直显示mmdjj
</div>
</template>
<script>
export default {
mounted() {
setTimeout(() => {
this.$root.testMeg = "welcome";
console.log(this.$root.testMeg); // welcome
}, 3000);
}
};
</script>
3)让它成为响应式
6. EventBus
eventBus并不是vue官方的名称,它是使用vue实例的$emit接口建立全局的事件监听机制,很多人巧妙的使用它来组件通信,这种思想来源于Android事件发布/订阅轻量级框架eventBus。但是这并不是vue最优的通信机制。
本质是新建一个空的vue实例,然后在这个实例中进行事件的发布和监听
1.创建
本质就是实例化一个空vue实例
// src/eventBus.js
import Vue from "vue"
const eventBus = new Vue()
export default eventBus
或者直接挂载到全局,连引入都可以省略
// main.js
import Vue from "vue"
Vue.prototype.$eventBus = new Vue()
2.使用
一般这种方式每个都会经历三个阶段,发起事件——>监听事件——>销毁事件
发起
// children1.vue
// 这是挂载到全局的版本
<script>\
export default {
mounted(){
// 这是单独文件的版本(注意:引入省略了)
eventBus.$emit("update", this.msg)
// 这是挂载到全局的版本
this.$eventBus.$emit("update", this.msg)
}
};
</script>
监听
// children2.vue
<script>
export default {
mounted(){
// 这是单独文件的版本(注意:引入省略了)
eventBus.$on("update", this.updateMsg(msg))
// 这是挂载到全局的版本
this.$eventBus.$on("update", this.updateMsg(msg))
},
methods: {
updateMsg(msg){
console.log(msg)
// todo
}
}
};
</script>
销毁
// children2.vue
<script>
export default {
beforeDestroy(){
// 这是单独文件的版本(注意:引入省略了)
eventBus.$off("update")
// 这是挂载到全局的版本
this.$eventBus.$off("update")
}
};
</script>
3.优化
这种情况下,我们发现每次调用都需要手动删除,然后不能够很方便地使用,因为前缀有些过长
我们可以通过复写对$on 、emit进行代理 的方式,来改写这个这个方法
用正则表达式,检查如果前缀带有global,人家就挂载到
然后通过mixin将所有的组件都混入删除方法
// plugin proEvent;
function pluginEvent(Vue){
if(pluginEvent.installed){
return;
}
pluginEvent.installed = true;
const vmEventMap = {}; // 储存各个vm中监听的事件
const eventMap = {}; // 储存各个事件在哪些vm中出现,存储._uid
const globalTest = /^global:/; // 检测是否需要全局事件
function mixinEvent(Vue){
const on = Vue.prototype.$on;
Vue.prototype.$on = function(eventName, fn){
const vm = this; // 保存当前this
if(Array.isArray(eventName)){ // 将事件列表进行单独处理
eventName.forEach((item)>={
vm.$on(item,fn); // 进入到else分支中
})
}else{
if(globalTest.test(eventName)){
// 如果是全局事件
(vmEventMap[vm._uid] || vmEventMap[vm._uid] = []).push(vm);
// 在vmEventMap中注册
(eventMap[eventName] || eventMap[eventName] = []).push(eventName)
}
on.apply(vm,item,fn);
}
}
const emit = Vue.prototype.$emit;
Vue.prototype.$emit = function(eventName, ..args){
if(globalTest.test(eventName)){
let vmList = vmEventMap[eventName] || []; //vmList当前事件所要emit的组件
vmList.forEach((item)=>{
emit.apply(item,[eventName,...args]); //
})
}else{
emit.apply(vm,[eventName,...args]);
}
}
}
function mixinBeforeDestory(Vue){
Vue.mixin({
beforeDestory(){
const vm = this;
events = vmEventMap[vm._uid] || [];
events.forEach((event)=>{
let index = eventMap[event].findIndex( item => item._uid === vm._uid);
eventMap.splice(index,1);
});
delete vmEventMap[vm._uid]; // 删除数组
// Object.entries 将对象变成数组形式
Object.entries(eventMap).forEach(
// 删除可能因当前vm删除后,对应eventName存在的空数组
([eventName,vmList])=>{
vmList.length || vmList[eventName]
}
)
}
})
}
mixinEvent(Vue)
mixinBeforeDestory(Vue);
}
7.隔代注入,provide和inject
依赖注入是在provide选项中提供要共享的数据,在inject选项中使用共享的数据。它也是官方首推在不使用vuex时隔代组件通信方式
1)使用
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 任何后代组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
需要知道的是,provide也可以是个函数,返回一个对象,此外,它也和props一样可以设置默认值
const Child = {
inject: {
foo: { default: 'foo' }
}
}
// 或者
const Child = {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
2)不明显的缺点
出于设计的考虑,依赖注入和$root一样,都是非响应式的数据模式
但是官方又说了,可以通过提供一个响应式的对象,来使注入的数据是响应式的。
// 父级组件提供 'foo'
var Provider = {
data(){
return {
obj: {name: 'mmdjj'}
}
},
provide: {
foo: this.obj
},
}
// 任何后代组件注入 'foo'
var Inject = {
template: "<div>{{foo.name}}</div>", // => 三秒后由mmdjj变成welcome
inject: ['foo'],
created () {
console.log(this.foo.name) // => "mmdjj"
setTimeout(() => {
this.foo.name = "welcome";
console.log(this.foo.name); // welcome
}, 3
000);
}
// ...
}
相关:
- https://zhuanlan.zhihu.com/p/102806009?utm_source=wechat_session&utm_medium=social&utm_oi=609474670068764672
- https://juejin.im/post/5b45971ff265da0f9c678b55
- https://github.com/jser-club/vue-event-proxy/blob/master/src/index.js
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/entries