总结下vue组件通信的几种方式
前言
这几天在写公司的业务,然后涉及到了组件之间的通信,通过这篇文章总结下vue组件通信的几种方式,做个记录。方便后续查阅。(在面试中出现的频率也比较高)
方式
自定义事件($emit)
我们遇到最多的就是父子间的通信,父组件通过props传值给子组件,然后子组件想修改props的值,但是vue是单向数据流,数据只能由父组件单向流向子组件。 不能由子组件修改父组件的数据,这会让数据难以维护,追踪。
如果子组件要修改,需要通知父组件去修改,这样父组件修改好后再传给子组件,保证了单向数据流。
主要是通过自定义事件来实现。
父组件把自定义事件传给子组件,然后子组件需要修改的时候通过$emit
触发自定义事件,然后把参数传入,父组件收到参数会对数据进行处理。
父组件
// father.vue
<template>
<div id="app">
<child :name="name" @setName="setName"></child>
</div>
</template>
<script>
import child from "@/components/child.vue"
export default {
components: {
child
},
data () {
return {
name: '答案'
}
},
methods: {
setName (name) {
this.name = name
}
}
}
</script>
子组件
//child.vue
<template>
<div>
{{name}}
<button @click="changeName">change</button>
</div>
</template>
<script>
export default {
props: {
name: {
name: String
}
},
methods: {
changeName () {
this.$emit('setName', 'cp3')
}
}
}
一开始name是答案
点击按钮,name就变成cp3
了。这样达到了子组件向父组件传值的效果。
如果你直接在changeName把name修改了,那么就会报错
当然,这个报错只针对赋值的情况,如果你的props是对象,修改对象的属性,不会报错,但是最好不要这样做。
$parent/$children
除了上面的自定义事件来实现父子组件通信,还可以通过 $parent/$children
访问到父子组件的实例,进而实现组件通信。
从字面上可以看出,$parent
是访问父组件的实例,$children
是访问子组件的实例。
父组件:
// father.vue
{
methods: {
setName (name) {
this.name = name
}
}
}
// child.vue
{
props: {
name: {
type: String
}
}
methods: {
setParentName (name) {
this.$parent.setName(name)
}
}
}
可以看到,当子组件调用setParentName
,会调用this.$parent.setName(name)
,先获取父组件实例,然后再获取到setName方法,达到调用到父组件的方法,和父组件通信。
$children
也是类似。这里不再赘述。
$parent/$children
适合一些逻辑简单些的父子组件通信。
如果组件逻辑复杂的,后期就不好维护,因为你有可能不知道为什么数据就变了,难以追溯。
而且如果组件的关系变了,不是父子关系了,就得把用到$parent/$children
的地方都要改。
$refs
除了 $parent/$children
, 还可以通过 $refs 来访问组件的实例。
如果ref
定义在普通的元素上,就是对应的dom元素。
如果定义在组件上,就是对应的组件实例。
// father.vue
<child ref="child"></child>
console.log(this.$refs.child) // 子组件的实例
console.log(this.$refs.child.name) // 实例的数据
console.log(this.$refs.child.getName()) // 实例的方法
这个$refs
是单向的,访问子组件,或者子组件的子组件等等,并不能往上访问父组件。
$refs
一般用来访问子组件就好了,跨多级访问写起来麻烦,可读性也不高。
事件总线(eventBus)
上面的自定义事件和parent/parent/parent/children是针对父子组件的,如果遇到兄弟组件,那么该怎么做?
可以使用事件总线(eventBus)。
新建个event.js文件,在里面新建个vue实例。
export default new Vue()
然后在需要相应的组件,导入该文件
监听的组件:
import event from './event.js'
event.$on('setName', (name) => {
this.name = name
})
触发的组件:
import event from './event.js'
event.$emit('setName', 'cp3')
这个思路也是自定义事件,$on
监听,$emit
触发,但是这个灵活性高一些,在对应的组件引入即可,可实现全局事件监听,适合非父子组件的监听。
vuex
事件总线适合一些非父子之间的简单通信。
如果你的页面很复杂的话,还是推荐vuex
。
vuex,全局管理组件的状态,我们可以把组件公共的数据定义在vuex上,如果修改公共的数据,调用vuex的相应方法,修改后的值会同步到相应的组件上。适合多组件的复杂通信。
vuex的几个概念:
-
state
定义公共数据的地方
-
getter
类似于计算属性,可以基于state的数据进行再次计算。
-
mutation
同步修改state
-
action
支持异步,通过调用mutation去修改state
-
module
模块,每个模块可以有自己的state,getter,mutation,action。如果组件过多,复杂,建议组件都有自己的模块,方便管理。
{
state: {
name: '答案cp3'
},
getters: {
fullName (state) {
return state.name
}
},
mutations: {
setName (state, params) {
state.name = params
}
},
actions: {
asyncSetName ({ commit }, params) {
// 模拟异步
setTimeout(() => {
commit('setName', params)
}, 200)
}
},
modules: {
moduleName // 使用时改成实际引入的名字
}
}
举个例子:
如果有3个组件需要state的name,则只需要setName执行一次,state的name更新,3个组件的name也会更新。
依赖注入(provide/inject)
这个比较少用,适合嵌套很深的组件。在父组件通过provide
定义数据,然后在子孙组件(不管多深)都能通过inject
拿到provide
定义的数据。
比props一层一层传好一点,它只需定义一次,子孙组件都能拿到。
// father
{
provide: {
obj: {name: '答案cp3'}
}
}
// son or grandson..
{
inject: ['obj'],
mounted() {
console.log(this.obj) // {name: '答案cp3'}
}
}
provide/inject并不是响应式的,所以大家用的时候需要注意⚠️
$attrs/$listerner
实话说,这两个属性我用的不多😂,我今天才看文档学的。所以我就简单讲2句,就2句。
我们传给组件的属性,如果组件没有通过props
接收所有属性,那么没有接收到的属性就会被绑定到根元素上。
如果我们不要绑定到根元素的行为,我们可以通过设置inheritAttrs
为false
来阻止继承。
我们可以通过this.$attrs
可以拿到这些属性。
然后还可以通过v-bind="$attrs"
继续传给子孙组件。
// father.vue
<child name="答案cp3" :isBoy="true"></child>
// child.vue
<grandson v-bind="$attrs"></grandson>
{
inheritAttrs: false,
props: {
name: String
},
mounted () {
console.log(this.$attrs) // {isBoy: true} 除了props的其它属性都在这里
}
}
$listerner
是把当前在组件上定义的事件都包裹在里面(不包含.native修饰的事件),然后在子组件通过$listerner
访问到父组件的方法,可以调用。
子组件可以通过v-on="$listerner"
继续传给下一代。
// father.vue
<child @input="inputEvent" :customEvent="customEvent"></child>
// child.vue
<grandson v-on="$listerner"></grandson>
{
props: {
name: String
},
mounted () {
console.log(this.$listerner) // {input: inputEvent, customEvent: customEvent}
this.$listerner.customEvent() // 执行父组件的方法
}
}
总结
总结这篇文章讲到的vue组件通信的方式
- 自定义事件($emit)
- $parent/$children
- $refs
- 事件总线(eventBus)
- vuex
- 依赖注入(provide/inject)
- $attrs/$listerner
转载自:https://juejin.cn/post/7083140749046317093