这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
讲通信,那么我们为什么要通信呀?
我们一般使⽤独⽴可复⽤的组件来构建⼤型应⽤,任意类型的应⽤界⾯都可以抽象为⼀个组件树,而在vue中使用组件化开发会变得更容易
那么为什么要使用组件化?
- 提⾼开发效率
- ⽅便重复使⽤
- 简化调试步骤
- 提升项⽬可维护性
- 便于多⼈协同开发
而涉及组件化的同时,必然会涉及通信,则是今天会讲到的知识点
vue2中通信大致分为以下几种:
- props
- on
- eventBus
- $parent
- $children
- $root
- $refs
- provide/inject
- $attrs
- $listeners
- vuex
props
子组件接收父组件的值使用props parent
<HelloWorld msg="hello world"/>
child
props: { msg: String }
msg可以指定type类型,require是否必须,default默认值等
需要注意的是如果数组默认为空需要用default:()=>{ return [] }
or
props: ['msg']
on
父子组件之间进行通信
parent
// template
<HelloWorld @sendMsg="sendMsg" />
// script
methods:{
sendMsg(msg){
console.log(msg) // this is msg
}
}
child
this.$emit("sendMsg","this is msg")
eventBus
eventBus,任意两个组件之间通信可以使用事件总线,就像打电话一样
- 手写eventBus
// bus.js
class Bus {
constructor() {
this.callbacks = {};
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus();
// child
this.$bus.$on('sendMsg',msg =>{ console.log(msg) })
this.$bus.$emit('sendMsg','this is msg')
- 新建一个eventBus.js文件
// eventBus.js
import Vue from 'vue'
export const EventBus = new Vue()
发送者
<script>
import { EventBus } from "../eventBus.js";
export default {
mounted() {
sendMsg() {
EventBus.$emit("sendMsg", 'this is msg');
}
}
};
</script>
接收者
import { EventBus } from "../eventBus.js";
export default {
mounted() {
EventBus.$on("sendMsg", (msg) => {
console.log(msg) // this is msg
});
}
};
</script>
- 直接使用 挂载
// main.js
Vue.prototype.$EventBus = new Vue()
使用
// 发送
this.$EventBus.$emit("sendMsg", 'this is msg')
// 接收
this.$EventBus.$on("sendMsg", (msg) => {
console.log(msg) // this is msg
});
root
兄弟组件之间通信可通过共同祖辈搭桥
// brother1
this.$parent.$on('msg', handle)
// brother2
this.$parent.$emit('msg')
兄弟组件可以使用 $parent
,没有直接关系使用 $root
有时不太复杂的逻辑没必要通过vuex管理,这个时候可以用$root可以获取父组件data中的值
parent
data() {
return {
msg: "this is msg"
};
},
child
console.log(this.$root.msg)
$children
⽗组件同样可以通过$children访问⼦组件实现⽗⼦通信
// parent
this.$children[0].xx = 'xxx'
注意:$children不能保证⼦元素顺序,因此官方并不推荐用
listeners
特性是⽗作⽤域中不作为 prop 被识别,通过 vbind="$attrs"
直接传⼊内部组件,这种用法比较少见,一般在创建高阶组件时会用到
- $attrs
grandpa
<Child msg="this is msg"></Child1>
child
<p>{{$attrs.msg}}</p>
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>
grandson
<div>{{msg}}</div>
console.log(msg) // this is msg
当grandpa传递给child,也就是gandson的父级时,并不需要在props里定义,通过vbind="$attrs"
直接传递给grandson
传递中v-bind可以将对象展开,父组件也可以使用
因为grandson的父组件可能压根不需要这些参数,通过这种方法,子孙组件可以直接获取到来自祖辈传递的值
- $listeners
grandson
<div @click="$emit('send-msg', 'this is grandson')"></div>
parent
<Grandson v-on="$listeners"></Grandson>
grandpa
// template
<Child @send-msg="sendSomeEvent" />
// methods
sendSomeEvent(msg){
console.log(msg) // this is grandson
}
同样的,当子孙想把消息传递给祖辈时,父组件也会在中间搭一座桥,通过v-on="$listeners"
直接传递给祖辈
provide/inject
provide/inject 能够实现祖先和后代之间传值
grandpa
export default {
provide() {
return {
foo: 'foo from grandpa',
}
},
}
grandson
// template
<div> {{foo}} </div>
// script
export default {
inject: ["foo"]
}
其中涉及的生命周期顺序:
- data
- provide
- created,在这个阶段$el还未生成,在这先处理privide的逻辑,子孙组件才可以取到inject值
- mounted
还需要注意的是:
Vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的
并且,不常用的原因其中有:
如果有多个后代组件同时依赖于一个祖先组件提供的状态,那么只要有一个组件修改了该状态,那么所有组件都会受到影响
其中涉及的强耦合,在多人协作时,可能会造成混乱,你根本不知道在哪修改了值,因此,在使用的时候一定要规划好,规定好作用域以及确认是一次性数据
那为什么又推出这个东西呢,其存在必然有道理,比如在组件开发的时候
想想封装一个Form的组件,其中有FormItem,再其中可能还会有Button,那么一层一层的关系嵌套下,无论是props还是vuex,都不再是最好方案,provide/inject将会是非常好的选择