前言
时间过得真快,这是2024年的第一篇文章,而马上随之而来的则是前端伙伴们瞩目的春招&远程实习,为了拿下这个赛点,我们今天开始来重温巩固一下vue组件间的通讯方式,这是一个非常基础且重要的知识点,如果这个知识点没有门清,用vue开展项目就是一手托天了。
在vue组件中有常见的三种通讯,分别是:
1. 父子组件通讯: 父组件 v-bind 绑定属性用于传值,子组件props接收。
2. 子父组件通信: 父组件订阅一个事件,子组件通过$emit发布该事件且携带事件参数,让父组件的订阅生效。
3. 兄弟组件通信: vuex
父子组件通信
在Vue中,父子组件通讯是通过props和v-bind实现的。父组件通过v-bind绑定属性,将数据传递给子组件,而子组件通过props接收这些属性。这形成了单向数据流,父组件负责向子组件传递数据,而子组件只能读取这些数据,不建议直接修改。接下来我们借助一个简单的todolist
来演示
App.vue -> 目标呈现出的页面
首先我们想要呈现出一个这样的页面:列表清单中首先有两条数据,当我们在输入框中输入数据并且点击确定后会在列表清单中更新数据,额外的优化就是不允许添加空任务,且每次添加完任务置空输入框,项目比较简单,很容易看懂。
xml
<template>
<div>
<div class="head">
<input type="text" v-model="message">
<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','css'],
message:'',
}
},
methods:{
submit(){
if(!this.message)return
else{
this.lists.push(this.message)
this.message=""
}
}
}
}
</script>
<style lang="css" scoped>
</style>
父组件绑定属性 子组件props接收
现在我们把原来APP.vue文件中的body分成一个组件出去写,比如就叫这个组件为List.vue。于是当我们在App.vue中引入注册使用完了这个List.vue后,App.vue就相当于父组件,而List.vue就相当于是子组件,我们需要做的就是子组件接收父组件传递的数据源,然后能够成功在页面显示。
父组件:嗨,小子,我有点东西要给你,拿好!
子组件:收到老爹,我知道怎么用,不会乱改的。
xml
<!-- 子组件 -->
<template>
<div class="body">
<ul>
<li v-for="(item,index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
props:['lists'] //用props来接收父组件传递的参数
}
</script>
xml
<!-- 父组件 -->
<template>
<div>
<div class="head">
<input type="text" name="" v-model="message">
<button @click="submit">确定</button>
</div>
<List :lists="lists" /> <!-- 3.使用 并且绑定数据向子组件传递参数 -->
</div>
</template>
<script>
import List from '@/components/body1/List.vue' // 1.导入
export default {
components: {
List // 2.注册
},
data() {
return {
lists: ['html', 'css'],
message: ''
}
},
methods: {
submit() {
if (!this.message) return
this.lists.push(this.message)
this.message = ''
}
}
}
</script>
<style lang="css" scoped></style>
子父组件通信
子父组件通信是通过事件实现的。父组件通过$on
方法订阅一个事件,而子组件通过$emit
发布该事件,并携带事件参数。这样,当子组件触发事件时,父组件就能捕捉到事件并执行相应的逻辑。
父组件:嘿,小家伙,有啥新花样吗?
子组件:老爹,我这儿有点小事,你听着。
xml
<!-- 子组件 -->
<template>
<div>
<div class="head">
<input type="text" v-model="message">
<button @click="submit">确定</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
message: ''
}
},
methods: {
submit() {
if (!this.message) return
//
this.$emit('add',this.message) //子组件发布事件 并且将message传给父组件使用
this.message = ''
}
}
}
</script>
xml
<!-- 父组件 -->
<template>
<div>
<Head @add="handle" /> <!-- 父组件订阅add事件 -->
<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','css'],
message:'',
}
},
methods:{
handle(val){
this.lists.push(val)
}
}
}
</script>
在这里,我们将输入框和按钮交给Head.vue这个子组件来做,那么子组件就需要履行它的职责,将获取到的输入的数据传给父组件,而父组件必须得时刻关注着子组件传过来的值呀,于是就需要订阅子组件发布的事件,随之进行处理。
兄弟组件通信
既然现在我们已经知道了父子组件如何通信,子父组件如何通信,那么我们接着来思考一下兄弟组件如何实现通信呢?
父组件作为中间人传递信息
欸,有小伙伴就会说了:这还不简单?直接让子组件先与父组件通信,接着父组件再和另外一个子组件通信不就OK了吗?
没错,我们确实可以这样做,但是我们再思考一个问题,在实际开发过程中,组件嵌套组件是很常见的,如果嵌套过于深了,这样做的代码可读性和复杂性将会大大提高!
所以这里我们就不选择这种方式,我们这样做的目的无非是因为父组件可以作为中间人负责信息的传递,但是缺点是过于繁琐,那么有没有一个工具能取其精华,去其糟粕呢?
有!接下来让我们来看看Vue自带的一个仓库------Vuex!
Vuex 扫清障碍 做回自己!
如今问题是:在复杂的应用中,兄弟组件通信可能会变得复杂。为了解决这个问题,Vue提供了Vuex,一个专门用于状态管理的库。通过Vuex,我们可以在不同的组件之间共享状态,实现兄弟组件之间的通信。
安装Vuex
那么如何安装vex? 我们有两种办法:直接下载 / CDN 引用
这里我们选的的是通过Node.js自带的包管理工具------npm来直接安装Vuex4.x版本。
其中 --save代表将包的信息保存到项目依赖中,即使在其他人或其他机器上使用该项目时,只需要启动项目就会自动安装我们这个包。
安装完成后,我们可以去package.json文件中查看依赖:
部署仓库
这里我用的是cli脚手架,所以需要手动创建一个仓库文件,我就在src/store目录下创建一个index.js,现在这个文件就相当于是我们的仓库了,我们的数据源和数据的操作方法都可以存在在这里面了,那么应该怎么使用呢?
js
// src/store/index.js
import { createStore } from "vuex" // 创建一个仓库
const store = createStore({ // 实例化仓库对象
state() { // data
return {
lists: ['html', 'css', 'js']
}
},
mutations: { //methods
listsAdd(state,val){
state.lists.push(val)
}
}
})
export default store // 抛出该仓库
在这里,我们就将需要用到的数据和方法在仓库中部署完毕了,在vuex语法中state
就相当于是data数据源,不过它是被所有组件所共享的,而mutations
则相当于methods。
不愧是Vue同款开发者,连风格都跟Vue最起初的选项式API风格一样。俗话说:没有规矩,不成方圆。
其实在vuex中除了mutations
之外还有一个actions
同样可以用来改变状态,但是这两者的不同在于mutations
只能存放同步事务
,而actions
可以存放异步事务
。
为什么这么做?一言蔽之: 在 mutation
中混合异步调用会导致你的程序很难调试。例如,当你调用了两个包含异步回调的 mutation
来改变状态,你怎么知道什么时候回调和哪个先回调呢?
这里,我们重点是了解vuex的语法风格,如此看来,这些规矩显得过于繁杂和死板了。
既然vue后来都有了组合式API选项,那么vuex怎会没有时代的替代者?下一篇文章我们去聊聊时代的先驱者------Pinia🍍
组件划分 各司其职
项目开发最重要的就是两个字------干净! 各组件应该各司其职,这样才能做到项目安全、容错率高、可维护性强。
所以我们,将输入框和确定按钮分为一个Head.vue组件而无序列表分成一个List.vue组件出去写。
js
//Head.vue
<template>
<div>
<div class="head">
<input type="text" name="" v-model="message">
<button @click="submit">确定</button>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex' // 1. 从仓库中拿到mutations中的所有方法
export default {
data(){
return {
message:'' // 2. 将获取到的数据抛出
}
},
methods:{
submit(){
if(!this.message) return
this.listsAdd(this.message) // 4. 调用listsAdd方法
this.message = ''
},
...mapMutations(['listsAdd']) // 3. 结构出listsAdd方法
}
}
</script>
js
// List.vue
<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' // 1. 拿到仓库中的state中的所有数据
export default {
computed:mapState(['lists']) // 2. 通过computed计算属性拿到仓库中 lists 数据
}
</script>
而我们的组件都已经规划完毕了,接下来要做的就是将这些相应的组件挂载到App.vue上,实际开发中,我们也可以将App.vue作为挂载对象或者创建一个router.vue作为挂载对象。
既然是挂载对象,所有的业务逻辑都不应该写在这个对象上,挂载对象只负责挂载!
js
// App.vue
<template>
<div>
<!-- 3. 使用组件 -->
<Head />
<List />
</div>
</template>
<script>
import Head from '@/components/body4/Head.vue' // 1. 导入组件
import List from '@/components/body4/List.vue'
export default {
components:{
Head, // 2. 注册组件
List
},
}
</script>
最终效果
总结
在这篇文章中,我们可以说是学习或者回顾了一遍Vue中组件交流的三种方式:
1. 父子组件通讯:父组件 v-bind 绑定属性用于传值,子组件props接收(props)是单向数据流,子组件只能用,不建议修改,改了父组件也不受影响。
2. 子父组件通信:父组件订阅一个事件,子组件通过$emit发布该事件且携带事件参数,让父组件的订阅生效
3. 兄弟组件通信: vuex