Vue 封装得很好,有很多API供我们选择,导致我这种选择困难户老纠结用哪种。。。
这次就来梳理一下
Vue
组件的通讯方式的使用场景
1. props
这个不需要多说了吧。爸爸和儿子组件最直接的通讯方式!
总结就是:爸爸在儿子身上绑定属性,儿子通过props
接收,再通过$emit
反馈给爸爸
// 爸爸
<Son word="好好做人" @msg="sonSay"/>
methods: {
sonSay(event) {
console.log(event); // 我不听
}
// 儿子
props: ['word']
methods: {
fn() {
console.log(this.word); // 好好做人
this.$emit(msg, '我不听');
}
}
优点:
props
是使用频率最多的一种通讯方式,因为它导致的整个事件触发的过程可以追溯(你可以通过Vue DevTools浏览器插件看到)。一切变化都明明白白!
缺点:
爷爷和孙子交流?哥哥和弟弟交流?弟弟和表哥的朋友的女朋友交流?
这些场景,用props
不是说不行,就是一层一层传递比较麻烦。。。
使用场景:
爸爸组件和儿子组件的通信,首选!不嫌麻烦,爷爷和孙子通信也可以。
但是如果关系比较疏远,就别用了,很费劲。。
2. $attrs 和 $listeners
一句话总结:$attrs 是能接收 非props
属性;而$listeners可以调用爸爸或者祖先的 methods
举个栗子:
组件父子情况:CompA --> CompB --> CompC --> CompD
// CompA
<div class="comp-a">
<!-- 将这两个东西传下去 -->
<CompB :num="num" @comp="handleComp"></CompB>
</div>
<script>
data: () => ({
num: 'aaa',
}),
methods: {
handleComp(newNum) {
console.log('hello, handleComp~~~', newNum) // 被CompD点击修改成 {num: "改成DDD"}
// 然后可以赋值给 num
this.num = newNum;
},
},
</script>
// CompB
<div class="comp-b">
<CompC v-bind="$attrs" v-on="$listeners"></CompC>
</div>
// CompC
<div class="comp-c">
<CompD v-bind="$attrs" v-on="$listeners"></CompD>
</div>
// CompD
<div class="comp-d">
<button @click="handleClick">DDD</button>
</div>
<script>
export default {
methods: {
handleClick() {
console.log('CompD click...')
console.log(this.$listeners)
this.$listeners.comp('改成DDD'); // 调用一层层传下来的 comp 函数
},
},
created(){
console.log(this.$attrs); // {num: "aaa"}
}
}
</script>
从CompA
中通过v-bind="$attrs"
一层一层的将num
属性传递给CompD
然后通过v-on="$listeners
一层一层的将handleComp
方法传递给CompD
CompD
通过this.$listeners.comp
直接调用CompA
的方法,从而修改num
属性
通过$attrs
处理过的组件,属性会附着在该组件的根元素上
其实个人觉得这种作风不太像Vue,Vue之前一般是发出一个通知 (props和$emit的处理方法),交给上级去处理。
反而有点像 React 的处理方法,通过调用上级的方法去修改属性。
但是它代码确实没有props
&$emit
臃肿,但是原理还是一层一层传递。
可以通过设置属性 inheritAttrs: false
,去掉
优缺点:
和第一种props
差不多,比第一种好就好在如果跨越组件很多层,传递稍微方便。
但是兄弟通讯还是不方便。
使用场景:
组件层次比较深,且组件都是“独生子”。
3. provide 和 inject
这种方法就相当于:发广播,接收广播
provide
向后代们发出广播,后代有需要的话,就可以接收广播(不需要就不接)
举个栗子:
组件父子情况:CompA --> CompB --> CompC --> CompD
// CompA.vue
<script>
data() => ({
hobbit: '打篮球',
}),
provide() {
return {
CompAHobbit: this.hobbit // CompA想把打篮球发扬光大...
}
}
</script>
组件A已经发布了广播了,此时,组件C想拿
// CompC.vue
<template>
<div> {{ CompAHobbit }} </div> <!-- 打篮球 -->
</template>
<script>
inject: ['CompAHobbit']
</script>
但是,此时的数据并不是响应式的,如果中途CompA
把hobbit
改成打羽毛球
,CompC
并不知道,显示的还是打篮球
。
所以说要想获得响应式的数据,可以这么写:
// CompA.vue
provide() {
return {
CompAHobbit: () => this.hobbit //广播一个函数,并把结果作为返回值
}
}
// CompC.vue
inject['CompAHobbit'],
computed: {
cHobbit() {
return this.CompAHobbit();
}
}
优点:
不再需要一层一层的传递数据了,后代想要就要
缺点:
后代只能接收,不能反馈
使用场景:
在一些比较深层的组件中,并且后代不需要反馈给祖先的一些组件库/插件中,也不太建议使用在普通的组件中!因为数据变幻莫测。
4. Event Bus
事件总线,就像一辆公共汽车,你想去见朋友,只要上车就行,然后告诉你朋友你要来了。司机会送你过去,到时候你朋友下来开门,你们就能见面了。
先创建一辆公共汽车
// Bus.js
class Bus{
es = {}
// 绑定事件
on(eventName, cb) {
this.es[eventName] = this.es[eventName] || [];
this.es[eventName].push(cb);
}
// 触发事件
emit(eventName, ...params) {
const listeners = this.es[eventName] || [];
listeners.forEach(listen => {
listen(...params);
});
}
}
export default new Bus();
或者利用vue这样创建:
// bus.js
import Vue from 'vue';
export const bus = new Vue();
或者直接全局创建:
Vue.prototype.$bus = new Vue();
比如两个兄弟组件通信:
// Son1.vue
methods: {
handleClick() {
this.$bus.$emit('son1-say', '我是哥哥');
}
}
Son1 发出信号,Son2监听(接收)
// Son2.vue
mounted() {
this.$bus.$on('son1-say', who => {
console.log(who); // 我是哥哥
}
},
// 记得在组件销毁前移除
beforeDestroy() {
this.$bus.$off('son1-say');
}
优点:
组件关系不是父子的话,这种方式比第一种方式props
来得更加方便。
缺点:
导致的整个事件触发的过程难以追溯,使用的组件一多,数据变化会非常莫名其妙。。
使用场景:
在一些组件库、插件中使用比较香,尽量不要在普通的组件中使用。。
比如有<MyForm />
和它的子组件们<MyInput />
、<MyButton />
、<MyRadio />
,在这个圈子之间传递数据是比较香的。但是脱离了这个生态圈,就别用了。
5. Vuex
重量级选手,能在Vue项目的一切场景使用,但是要安装,成本非常高。
就不详细描述了,大概就是建立一个商店,然后全世界的人都能来这里买卖、自由交易,所有的交易都会被一一记录(Vue DevTools 浏览器插件可以回溯)
具体食用还是看官网吧:Vuex 是什么? | Vuex (vuejs.org)
优点:
大范围杀伤力,无论两个组件隔开有多远,关系多么复杂,都能通信!
缺点:
成本高,Vue官方都说小项目没必要...
使用场景:
如果项目中,两个隔开很远,关系很复杂的组件经常通信,那就用。
比如说,项目一进来,就要获取身份,然后全局共享身份,此时Vuex是很香的。普通组件,插件等,都能用。
总结
- 但是如果是父子关系的,首选第一种方式:
props
- 在封装组件/插件中,兄弟关系的,或者关系略疏远的(比如后代向祖先反馈信息),选择第四种方式:
Event Bus
- 插件/普通组件中,祖先和后代(非兄弟)关系的,层次比较深,并且后代只接收不反馈的,选择第三中方式:
provide、inject
;如果需要反馈的,可以联合Event Bus
;或者选择第二种方式:$attrs、$listeners
。但是这两种方式不要滥用!!! - 普通组件,兄弟通信,你可以
props
&$emit
连用,兄弟经常通信,或者通信稍微复杂的,还是上Vuex
吧。
个人喜好:
小项目:props + event Bus
中大项目:props + vuex
其他的,着实鸡肋。。