前言
Vue.js 是当下前端开发领域非常热门的一个 MVVM 框架,也是组件化开发的代表。 在 Vue.js 中,组件是构成应用程序的基本单元。 Vue.js 中的组件具有树形结构,每个组件都可以有自己的状态、行为和模板。正因为这个原因,组件间的通信是一个很重要的问题。
本篇博客将分别介绍Vue中几种组件之间通信的方式以及相应代码实现。
组件之间的关系
在了解组件的通信方式之前,我们需要提前了解组件之间存在什么关系,在这个基础上我们将很自然地理解组件的通信依据,方便我们深入理解组件通信。
组件之间的关系主要包括以下几种方式:
- 父子组件
- 兄弟组件
- 跨级组件
- 非父子组件
在上图中,A组件和B组件,B组件和C组件,B组件和D组件存在父子关系; C组件和D组件存在兄弟关系; A组件分别和C组件和D组件存在跨级关系
组件之间的通信方式
1. props
在vue项目开发过程中,props
进行组件通信是非常常用的方式。
父组件向子组件传递数据,子组件可以使用 props
接收父组件传递过来的数据。
注意props中数据流是单项的,即子组件不可改变父组件传来的值
<!-- 父组件 -->
<template>
<div>
<child :message="msg"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: 'Parent',
components: {
Child
},
data() {
return {
msg: 'Hello, Child!'
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
{{ message }}
</div>
</template>
<script>
export default {
name: 'Child',
props: {
message: String
}
}
</script>
2. emit
emit方式也是Vue中最常见的组件通信方式之一,该方式用于 子传父
。
<!-- 子组件 -->
<template>
<div>
<button @click="sendMessage">Send Message</button>
</div>
</template>
<script>
export default {
name: 'Child',
methods: {
sendMessage() {
this.$emit('send-message', 'Hello, Parent!')
}
}
}
</script>
<!-- 父组件 -->
<template>
<div>
<child @send-message="receiveMessage"></child>
<div>{{ message }}</div>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: 'Parent',
components: {
Child
},
data() {
return {
message: ''
}
},
methods: {
receiveMessage(msg) {
this.message = msg
}
}
}
</script>
3. v-model
v-model是Vue.js提供的一种基于语法糖的组件通信方式。 通过在自定义组件上使用v-model,并在组件内部实现对应的事件和属性,可以方便地在父子组件之间双向绑定数据。
<!-- 父组件 -->
<template>
<div>
<child v-model="message"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
name: 'Parent',
components: {
Child
},
data() {
return {
message: ''
}
}
}
</script>
<!-- 子组件 -->
<template>
<div>
<input :value="value" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default {
name: 'Child',
props: ['value']
}
</script>
这个例子中,在子组件中声明了一个叫做value的props,用来接收父组件传递给它的值。 同时,在子组件的模板中,我们使用v-bind动态绑定了input元素的value属性,将value的值渲染到input元素中; 同时,我们监听了input事件,并通过$emit方法触发了一个名称为’input'的自定义事件,并将输入框的value作为参数传入。
到这里,我们其实已经将v-model的工作原理给全部实现了, 但是为了方便使用,Vue.js将v-model封装成了语法糖,通常情况下会要求自定义组件同时具备value属性和input事件。 因此,在出于习惯或规范上的考虑,我们可以在子组件中直接使用v-model。
<!-- 子组件 -->
<template>
<div>
<input v-model="value">
</div>
</template>
<script>
export default {
name: 'Child',
props: ['value']
}
</script>
这个例子中,我们直接使用v-model指令,Vue.js会将它转化为类似上面子组件的方式,来完成父子组件之间的数据双向绑定。
需要注意的是,当我们在自定义组件上使用v-model时, 它的实际作用是:将prop变成了一个具有监听器的计算属性,并且这个监听器负责把数据同步回去。 如果我们不给自定义组件声明名为“value”的prop,那么它内部的v-model就无法正常地工作。
4. provide/inject
provide/inject是祖先组件向后代组件传递数据的方式,祖先组件使用provide来提供数据,后代组件使用inject来注入数据,是一种高级特性。
<!-- 父组件 -->
<script>
import Child from './Child.vue'
export default {
components: {
Child
},
data() {
return {
msg: '子组件msg',
}
},
provide() {
return {
msg: this.msg,
}
}
}
</script>
<!-- 子组件 -->
<script>
export default {
inject:['msg'],
created(){
//获取高层级提供的属性
console.log(this.msg)
}
}
</script>
provide和inject并不是响应式的。也就是说,如果provide的数据或方法发生了变化,子孙组件并不会自动地接收这些变化。因此,它们通常被用来传递静态的数据或方法。
5. ref
ref是Vue中一种常见的组件通信方式。它可以在子组件中访问父组件中的DOM元素或子组件实例。
在Vue2中,ref可以用来获取DOM元素或子组件实例。在模板中使用ref指令声明一个引用名称,然后在组件实例中通过this.$refs访问该引用。
<!-- 父组件 -->
<template>
<div>
<!-- 获取DOM元素 -->
<input type="text" ref="inputRef" />
<!-- 获取子组件实例 -->
<Child ref="childRef"></Child>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child,
},
mounted() {
// 访问DOM元素
this.$refs.inputRef.focus();
// 访问子组件实例
this.$refs.childRef.doSomething();
},
};
</script>
<!-- 子组件 -->
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true,
},
},
methods: {
doSomething() {
console.log('子组件执行了某些操作');
},
},
};
</script>
在Vue3中,ref的使用方式有所改变。现在,ref可以用来获取DOM元素、子组件实例、或父组件实例。 此外在Vue3中如果要获取父组件实例,ref指令需要在父组件上声明,而不是在子组件上。
<!-- 父组件 -->
<template>
<div ref="parentRef">
<!-- 获取DOM元素 -->
<input type="text" ref="inputRef" />
<!-- 获取子组件实例 -->
<Child ref="childRef"></Child>
</div>
</template>
<script>
import Child from './Child.vue';
export default {
components: {
Child,
},
mounted() {
// 访问DOM元素
this.$refs.inputRef.focus();
// 访问子组件实例
this.$refs.childRef.doSomething();
// 访问父组件实例
this.$refs.parentRef.doSomething();
},
methods: {
doSomething() {
console.log('父组件执行了某些操作');
},
},
};
</script>
<!-- 子组件 -->
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true,
},
},
methods: {
doSomething() {
console.log('子组件执行了某些操作');
},
},
};
</script>
6. EventBus/mitt
EventBus是一种全局事件通信的方式,在Vue.js中,我们可以通过创建一个Vue实例来进行事件的监听,触发和销毁,从而在组件之间进行通信。
创建一个EventBus对象。可以在单独的.js文件中创建,然后在Vue组件中引入使用。这里简单地在Vue实例中创建:
var EventBus = new Vue();
在需要发送消息的组件中,调用EventBus.$emit()方法来发送消息:
// 发送消息
EventBus.$emit('message', { text: 'hello world' });
在需要接收消息的组件中,通过调用EventBus.$on()方法来监听消息:
// 监听消息
EventBus.$on('message', function(data) {
console.log('received message:', data.text);
});
这样就可以在不同的组件之间传递消息了。
注意:为了避免内存泄漏,需要在组件销毁前,调用
EventBus.$off()
方法来解除监听。
在Vue3中, EventBus
已经移除,取代的是 mitt
,但其本质也是基于 EventBus
。
首先需要安装mitt库:
npm install mitt
然后,在需要进行跨组件通信的组件中,引入mitt并创建一个事件总线对象:
import mitt from 'mitt';
const bus = mitt();
在需要发送消息的组件中,通过调用bus.emit方法来发送消息:
// 发送消息
bus.emit('message', { text: 'hello world' });
在需要接收消息的组件中,通过调用bus.on方法来监听消息:
// 监听消息
bus.on('message', (data) => {
console.log('received message:', data.text);
});
这样就可以在不同的组件之间传递消息了。如果你想要全局使用这个事件总线,你可以将其绑定到Vue.prototype
上:
Vue.prototype.$bus = bus;
这样,在任何组件中,都可以通过this.$bus来访问这个事件总线对象,从而实现跨组件通信。
7. Vuex
Vuex是一个专为Vue.js应用程序开发的状态管理模式,在Vuex中,我们将共享的数据存储在全局的store中,并使用getter、mutation、action来进行状态管理。
关于Vuex的详细内容请看官方文档,另外建议在组件通信中减少使用Vuex方式,使用Vuex可能会导致组件之间的耦合性增加,因为它们需要共享全局状态。这使得重构和更改变得困难。
<!-- 组件A -->
<template>
<div>
<p>{{ counter }}</p>
<button @click="increment">increment</button>
</div>
</template>
<script>
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState({
counter: (state) => state.counter,
}),
},
methods: {
...mapMutations(['increment']),
},
};
</script>
<!-- vuex store -->
<script>
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
counter: 0,
},
mutations: {
increment(state) {
state.counter++;
},
},
});
</script>
总结
以上是Vue.js中常用的组件通信方式,当然还有其他的方式如attrs/listeners
、parent/children
等,但并不常用。对于每一种方式,我们需要根据实际场景选择合适的通信方式,从而实现组件之间的高效通信。