vue组件通信方式也是面试热点问题,这个东西非常的重要,如果不懂通信那项目就无法开展下去,今天我们就来一起认识下vue组件的最基本的三种方式
- 父子组件通信:父组件v-bind绑定属性用于传值,子组件props接收
- 子父组件通信:父组件订阅一个事件,子组件通过$emit发布该事件,且携带事件参数,让父组件的订阅生效
- 兄弟组件通信:vuex
为了方法讲解,我们引入一个简单的栗子----todolist
准备工作
利用cli脚手架创建一个项目
vue create component-communicate
// 后面的选项记得勾选vuex
我们在App.vue中简单实现一个todolist(没写css,主讲原理
App.vue
<template>
<div>
<div class="head">
<input type="text" name="" 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() {
// console.log(this.message);
if(this.message) {
this.lists.push(this.message);
this.message = ''
}
}
}
}
</script>
<style lang="css" scoped>
</style>
head输入框中的输入数据message需要给到list数组,体现在method中的方法
这里我分别把head输入框和body列表分成两个组件,在App.vue中保留head,把body List分出去成为一个components组件,此时App.vue就相当于父组件,body List相当于子组件,head向body List传值就是下面的父向子通信。也就是说list这个数组需要传给子组件去展示。
一个小知识lintOnSave
这里我想多用几个App.vue来举例,需要命名App1.vue、App2.vue的时候,项目可能会报错,我们需要去vue.config.js这个配置文件去加一行代码
lintOnSave: false
因为cli脚手架对你组件的命名要求很严格,将lintOnSave
的值改为false就可随意取名,配置改完需要将项目重启一下。你想使用哪个App.vue只需要去main.js中from中修改就可以
父向子组件通信
父组件的list值传给子组件
父组件v-bind绑定属性用于传值,子组件props接收
props其实就是一种数据源,data是正数据源,props是负数据源,props只接受父组件的东西
现在我把body List分出一个组件来写
List.vue子组件props接收
props中list就是父组件传过来的值
<template>
<div class="body">
<ul>
<li v-for="(item, index) in lists" :key="index">{{ item }}</li>
</ul>
<button @click="changeProps">修改props</button>
</div>
</template>
<script>
export default {
props: ['lists'],
}
</script>
<style lang="css" scoped>
</style>
props单向数据流
props遵循着单向绑定原则,props因父组件的更新而变化,自然地向下流下组件……且看文档介绍
子组件只能用props的值,不建议修改,因为数据流会变得很混乱。改props可以行得通,但是父组件无法监听到,不是双向绑定
App.vue父组件绑定属性用于传值
绑定属性之前你需要将List子组件导入进来
导入一个组件的三个步骤
导入一个组件有三个步骤
- import路径引入
- components中声明
- 在template中使用,这里记得绑定属性
App.vue
<template>
<div>
<div class="head">
<input type="text" name="" v-model="message">
<button @click="submit">确定</button>
</div>
<List :lists="lists"/>
</div>
</template>
<script>
import List from '@/components/body1/List.vue'
export default {
components: {
List
},
data() {
return {
lists: ['html', 'js'],
message: ''
}
},
methods: {
submit() {
if(this.message) {
this.lists.push(this.message);
this.message = ''
}
}
}
}
</script>
<style lang="css" scoped>
</style>
子向父组件通信
现在我将head头部单独分离成一个组件,也就是head中的message需要传给父组件来展示
App.vue父组件订阅一个事件
只要子组件发布了订阅,父组件就会触发该事件
注意:导入head的时候注意一定是大写的Head,不能写成小写的,会与html5的head标签冲突。
App.vue
<template>
<div>
<Head @add="handle"/>
<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']
}
},
methods: {
handle(val) {
this.lists.push(val)
}
}
}
</script>
<style lang="css" scoped>
</style>
handle触发的条件就是有了add,里面的参数val就是子组件传过来的message
Head.vue子组件通过$emit发布该事件,且携带参数
$emit两个参数分别为抛出来的事件和抛出来的参数
emit文档:组件实例 | Vue.js (vuejs.org)
Head.vue
<template>
<div class="head">
<input type="text" name="" v-model="message">
<button @click="submit">确定</button>
</div>
</template>
<script>
export default {
data() {
return {
message: ''
}
},
methods: {
submit() {
if(this.message){
this.$emit('add', this.message)
this.message = '';
}
}
}
}
</script>
<style lang="css" scoped>
</style>
兄弟组件通信
既然父向子,子向父你会了,兄弟组件你就可以利用父做中间人,实现兄弟组件通信
父做中间人
Head和List都分离出去,然后Head将message发布订阅,父组件接收订阅以及参数,然后传给List子组件的props中,通过watch监听它的变化,变化之后改变数据源中的lists即可
Head.vue
<template>
<div class="head">
<input type="text" name="" v-model="message">
<button @click="submit">确定</button>
</div>
</template>
<script>
export default {
data() {
return {
message: ''
}
},
methods: {
submit() {
if(!this.message) return
this.$emit('add', this.message)
this.message = '';
}
}
}
</script>
<style lang="css" scoped>
</style>
List.vue
<template>
<div class="body">
<ul>
<li v-for="(item, index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: ''
}
},
watch: {
msg(newVal){
this.lists.push(newVal)
}
},
data() {
return {
lists: ['html', 'js']
}
}
}
</script>
<style lang="css" scoped>
</style>
App.vue
<template>
<div>
<Head @add="handle"/>
<List :msg="msg"/>
</div>
</template>
<script>
import Head from '@/components/body3/Head.vue'
import List from '@/components/body3/List.vue'
export default {
components: {
Head,
List
},
data() {
return {
msg: ''
}
},
methods: {
handle(val) {
console.log(val)
this.msg = val
}
}
}
</script>
<style lang="css" scoped>
</style>
vuex
通过父组件做中间人来进行兄弟组件之间的传参是非常麻烦的,为什么这么说,一旦项目大了起来,就相当于一个二叉树,最外层两端的叶子组件,进行通信,就会一直去找父组件,非常难受。因此vue作者尤雨溪又打造了一个vuex,它就是一个仓库,供组件使用
vuex文档:开始 | Vuex (vuejs.org)。vue3对应vuex4
vuex是为optionsAPI打造的,它是个仓库,可以被任何一个组件使用
安装
这里我们直接用npm安装就好,从这个指令可以看出--save
就说明项目完成后还需要这个仓库,就是运行的时候要用这个嘛,合乎逻辑!
如果你在用cli安装项目的时候,选中了vuex,他就会给你在src目录下生成一个store文件夹,里面会有个index.js文件,跟路由的形式很像。
src/store/index.js
import { createStore } from 'vuex' // 从vuex中引入一个createStore
const store = createStore({
// data
state() {
return {
lists: ['html', 'css', 'js']
}
},
// methods
mutations: {
listsAdd(state, val) {
// 修改仓库的数据源
state.lists.push(val)
}
}
})
export default store
state是仓库的数据源data,所以List.vue中就不需要自己再弄个数据源了,直接用仓库的数据即可,而仓库的数据源又来自Head.vue
mutations是仓库的methods,给一个修改数据源的方法,这里注意,mutations的方法不能用this,并且mutations里面的方法一定会有个形参,就代表数据源,第二个参数是人为传进的,这里就是传进的输入框信息
记得在main.js中引入再use掉,简直跟路由一模一样的
import store from './store'
createApp(App).use(store).mount('#app')
此时的App.vue写法只需如下
App.vue
<template>
<div>
<Head @add="handle"/>
<List :msg="msg"/>
</div>
</template>
<script>
import Head from '@/components/body4/Head.vue'
import List from '@/components/body4/List.vue'
export default {
components: {
Head,
List
}
}
</script>
<style lang="css" scoped>
</style>
此时的List.vue写法如下
List.vue
<template>
<div class="body">
<ul>
<li v-for="(item, index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: mapState(['lists']) // mapState会返回lists
}
</script>
<style lang="css" scoped>
</style>
import { mapState } from 'vuex'
就是引入仓库的数据源
mapState
函数中的参数就是你想要从仓库取出的数据源,并且给你返回出来给到计算属性computed
此时的Head.vue写法如下
Head.vue
<template>
<div class="head">
<input type="text" name="" v-model="message">
<button @click="submit">确定</button>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
methods: {
submit() {
if(!this.message) return
this.listsAdd(this.message)
},
...mapMutations(['listsAdd'])
}
}
</script>
<style lang="css" scoped>
</style>
这里不是从仓库拿到数据源再push,仓库的数据源只能仓库的方法来动,所以我们组件拿到仓库的方法listAdd就好
import { mapMutations } from 'vuex'
就是引入仓库的方法
...mapMutations(['listsAdd'])
解构listAdd函数的使用,为何解构?因为mutations的方法可能会有很多个,方法有了,现在搬到submit中去调用它
以上方法,不算“父做中间人”有三种,这三种方法是最基本的,组件通信有7,8种方法,不过你会这三种就足够你开发了
另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请”点赞+评论+收藏“一键三连,感谢支持!
本次学习代码已上传至本人GitHub学习仓库:github.com/DolphinFeng…