前言
在月初时,我开始了我的Vue3学习之旅,作为一名大三学生,刚接触到它就能明显感觉到它带给我的便利,相比于之前在学校学的Html+css+js三个文件开发前端页面,显然Vue给我带来了十分不一样的体验。最近我新学习到了Vue中组件之间如何进行通讯,这个问题,在下述文章中我会和大家一起去解决。
一.Vue组件通讯
- Vue的组件通讯归纳总结为以下三种:1.父子组件通讯 2.子父组件通讯 3.两个任意组件进行通讯
下面我会举出案例,一个简单的todo_list,只是我们将它分为头部head 和 内容部分body,然后我们会将head和body分别拆成单独的组件,来探究一下它们之间是如何进行通讯传值的。
总体代码:
App.vue:
vue
<template>
<div>
<div class="head">
<input type="text" name="" v-model="message">
<button type="button" @click="submit">确定</button>
</div>
<div class="body">
<ul>
<li v-for="(item, index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
data() {
return {
lists: ['html'],
message: ''
}
},
methods: {
submit() {
if (!this.message) return
this.lists.push(this.message);
this.message = ''
}
}
}
</script>
上述代码可以实现下述效果:
下面我们将body部分分离成一个组件,如下:
xml
<template>
<div>
<div class="head">
<input type="text" name="" v-model="message">
<button type="button" @click="submit">确定</button>
</div>
<List />
</div>
</template>
List.vue
我们增加了一个button按钮,等下用来对父组件的值进行修改。以上内容(上述代码目前还不能传值,我们还没有进行设置)就是我们下面要讲的第一种组件通讯方式-父子组件通讯。
1.父子组件通讯
- 父组件v-bind绑定属性用于传值,子组件props接受 (props是单向数据流,子组件只能用,不建议修改,改了父组件也无法感应到)
代码实现:
APP.vue修改(JS中进行注册这里就不列出来了):
List.vue修改:
效果显然依旧能实现:
!!!为什么点击修改props却能对父组件内容进行修改?刚刚不是说props是单向数据流吗。
- 其实,在Vue中,确实是不建议在子组件内直接修改props的值。这是因为props是父组件向子组件传递数据的一种方式,它是单向数据流的,即数据流动的方向是从父组件到子组件。子组件可以通过props接收父组件传递的数据,但应当将这些数据视为不可变的。当子组件试图直接修改props的值时,实际上是在试图违反这种单向数据流。这样的修改虽然在技术上是可行的,但是会导致以下问题:
- 可维护性差: 在Vue中,我们更倾向于让数据的变化在组件内部进行,而不是由外部传递。如果子组件修改了props,那么代码维护起来就会更加困难,因为我们期望props是只读的。
- 追踪问题困难: 如果子组件直接修改了props,那么可能会引起一些不可预测的行为,因为父组件无法感应到这种变化。这样的问题可能会很难追踪和调试
接着我们将head分离出去,成为一个组件。也就是实现第二种Vue组件通讯-子父组件通讯
Head.vue:
App.vue:
xml
<template>
<div>
<Head />
<div class="body">
<ul>
<li v-for="(item,index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script>
import Head from '@/components/body2/Head.vue'
export default {
components: {
Head
},
data() {
return {
lists: ['html'],
}
},
methods:{
}
}
</script>
<style lang="less"></style>
上述代码显然不能将数据传递过去,这次我们换成子组件给父组件传值,如下:
2.子父组件通讯
- 父组件订阅一个事件,子组件通过$emit发布该事件且携带事件参数,让父组件的订阅生效。
如何实现?
父组件订阅一个事件:
子组件通过$emit发布该事件:
当点击提交时触发submit函数运行,this.$emit('add',this.message)
会触发父组件的add事件也就是父组件中handle函数的执行,并传递一个参数this.message给父组件作为val的值。
我们仍然能够实现之前的功能,如下:
你是否想过一个问题呢,如今我们实现了父子组件通讯和子父组件通讯,我们是否可以完成任意组件的通讯呢,按理来说是可以的,不过只是需要套很多层就是了,例如有两个组件可能是某个组件的孙子组件,我们通过一层一层关系的传递,子传父,父传子,最终可以实现这两个组件的通讯。显然,可以实现,但是不太现实。层次少还好说,一旦多起来,就会很麻烦。那么Vue中是否存在可以实现两个任意组件的通讯呢。答案是可以的,这就是我们接下来要讲的第三种组件通讯方式- 任意两个组件之间实现通讯。
3. 两个任意组件的通讯
- 在Vue中,两个任意组件进行通讯可以通过使用状态管理工具或者事件总线来实现。
3.1 通过状态管理工具Vuex
在使用Vuex之前,我们需要先安装它。在项目文件的集成终端中输入
lua
npm install vuex@next --save
使用Yarn的输入下面内容
sql
yarn add vuex@next --save
安装完Vuex后,让我们来创建一个 store。创建过程直截了当------仅需要提供一个初始 state 对象和一些 mutation:
php
import { createApp } from 'vue'
import { createStore } from 'vuex'
// 创建一个新的 store 实例
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
// 下面部分我们在main.js中直接调用use即可app.use(store)
const app = createApp({ /* 根组件 */ })
// 将 store 实例作为插件安装
app.use(store)
现在,你可以通过 store.state
来获取状态对象,并通过 store.commit
方法触发状态变更:
arduino
store.commit('increment')
console.log(store.state.count) // -> 1
在 Vue 组件中, 可以通过 this.$store
访问store实例。现在我们可以从组件的方法提交一个变更:
javascript
methods: {
increment() {
this.$store.commit('increment')
console.log(this.$store.state.count)
}
}
再次强调,我们通过提交 mutation 的方式,而非直接改变
store.state.count
,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。
以上3.1的内容全来自Vuex的官方文档的使用方法,网站如下:(开始 | Vuex),下面,我将会将上述todo_list将head和body两个组件都拆分出去,并实现它们之间的通讯。
实现 =>
第一步:首先创建我们自己的store.js(前提已经安装好了Vuex哈),store.js通常放在src下面的store文件夹中。 代码如下(解释写在注释中,就不单独列出来了):
javascript
// 引入createStore函数,该函数用与创建Vuex store实例
import { createStore } from 'vuex'
// 创建实例
const store = createStore({
// state是仓库里面的数据源,所有页面公有的
// 但其他页面不能改变这里的数据
state(){
return {
lists:['html','css','js']
}
},
// mutations 类似 Vue中的methods
// 我们想修改仓库中的数据只能通过自己在mutations中写的方法进行修改,外部想办法拿到这个方法
mutations:{ // methods
listsAdd(state,val){
if(!val) return
state.lists.push(val);
}
}
})
export default store
第二步:Head.vue
xml
<script>
// 导入 Vuex 中的 mapMutations 辅助函数
import { mapMutations } from 'vuex'
// 导出 Vue 组件
export default {
// 数据部分
data(){
return {
message: ''
}
},
// 方法部分
methods:{
submit(){
// 调用 Vuex store 中的 listsAdd mutation 方法,将输入的数据添加到 Vuex store 中的 lists 状态中
this.listsAdd(this.message);
this.message = ''
},
// 使用 mapMutations 辅助函数,将 listsAdd mutation 映射到组件的 methods 中
...mapMutations(['listsAdd'])
}
}
</script>
mapMutations
是 Vuex 提供的一个辅助函数,用于在 Vue 组件中简化对 mutations 的调用。具体可查看官方文档,Mutation | Vuex (vuejs.org)) 。它的作用是将Vuex store 中的 mutations 映射到组件的 methods 中,使得组件可以直接调用 mutations,而不需要手动触发 store.commit
。这样做可以提高代码的可读性和简洁性。
第三步:List.vue
xml
<template>
<div>
<div class="body">
<ul>
<li v-for="(item, index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
// 取出公共数据源中的lists
computed: mapState(['lists'])
}
</script>
<style lang="less" scoped></style>
mapState
是 Vuex 提供的一个辅助函数,用于在 Vue 组件中简化对 Vuex store 中状态的映射。它允许你将 Vuex store 中的状态直接映射到组件的计算属性中,使得组件可以更轻松地访问和使用这些状态。具体也可以去查看官方文档-(State | Vuex (vuejs.org))
最后是App.vue
一开始的效果仍然能够实现:
3.2 通过事件总线
- 事件总线是一种在Vue.js中用于组件通讯的模式,它允许任意两个组件之间进行通讯,而不需要直接引用对方。在Vue中,我们可以使用Vue实例作为事件总线,通过它来发布(emit)和订阅(on)自定义事件,实现组件之间的解耦。
php
// 创建事件总线
const eventBus = new Vue();
// 组件A
eventBus.$emit('customEvent', data);
// 组件B
eventBus.$on('customEvent', handleEvent);
例如:
xml
<!-- 组件A -->
<template>
<div>
<button @click="sendData">Send Data</button>
</div>
</template>
<script>
export default {
methods: {
sendData() {
// 发布自定义事件,携带数据
eventBus.$emit('customEvent', 'Hello from Component A');
}
}
};
</script>
xml
<!-- 组件B -->
<template>
<div>
<p>Received data: {{ receivedData }}</p>
</div>
</template>
<script>
export default {
data() {
return {
receivedData: ''
};
},
mounted() {
// 订阅自定义事件
eventBus.$on('customEvent', this.handleEvent);
},
methods: {
// 事件处理函数
handleEvent(data) {
this.receivedData = data;
}
},
beforeDestroy() {
// 在组件销毁前取消订阅,防止内存泄漏
eventBus.$off('customEvent', this.handleEvent);
}
};
</script>