Vue组件通信
我们在上一篇文章已经学习如何使用vue组件和父子组件的基础概念了![vue]vue组件开发基础-组件基础,父子组件基础 - 掘金 (juejin.cn)
今天,我们就来带大家学习一下vue中的组件通信!注意,这个是我们在面试当中必考的内容!
vue组件通信有非常多的方式!
一、props 和 $emit
props 父组件向子组件传值
父组件要如何向子组件传一个值呢?这里,我们就要介绍一下props
props属性 :在Vue.js中,
props
是一种用于从父组件向子组件传递数据的机制。通过props
,父组件可以向子组件传递数据,使得子组件能够接收并使用这些数据。
我们可以使用v-bind在组件上绑定一个属性就可以将父组件中的数据传到子组件当中。
html
<!-- 我们可以使用v-bind绑定属性将数据传给子组件 "lists"代表的就是数据源中的lists数据 -->
<List :lists = "lists"/>
<!-- 注意!这里绑定时要保持一致 :lists="lists" :msg="msg" -->
子组件又如何拿到父组件传过来的数据呢?
在上篇文章我们已经介绍到defineProps API
defineProps API :在Vue 3中,
defineProps
函数是用于在函数式组件中声明和获取 props 的工具。所以我们可以在子组件中导入defineProps 函数,通过const {users} = defineProps(['users'])
过解构赋值来获取users。也就是数据,
js
// defineProps api 获取父组件传过来的参数
import { defineProps } from 'vue';
const {users} = defineProps(['users'])//users父组件传过来的数据的名称
我们再介绍一种简单的语法:
js
props:['lists']//lists是父组件传过来数据的名称,这里不仅可以是数组也可以是对象
具体如何使用,我们来看一个案例:
html
<!-- App.vue -->
<template>
<div>
<div class="head">
<input type="text" v-model="message">
<button @click="submit">确定</button>
</div>
<List :lists = "lists"/>
</div>
</template>
<script>
import List from './components/List.vue'
export default {
// 声明组件
components: {
List
},
// 数据源
data() {
return {
lists: ['html', 'css'],
message: ''
}
},
// 方法
methods: {
submit() {
if (this.message) {
this.lists.push(this.message)
this.message = ''
}
},
},
// 监听子组件是否修改 (props是单向绑定原则,单向数据流)
watch:{
lists(newVal){
console.log(newVal);
}
}
}
</script>
我们想要实现一个怎么样的效果呢?在页面上有一个input输入框,有一个确定按钮,我们通过点击提交能将数据同步到数据源中,子组件能够读到这份数据,并显示在页面上。
我们在App.vue中引入一个名为List.vue的组件,并装载到页面上
引入组件 :我们通过import List from './components/List.vue'
引入组件之后,要声明一下组件,使用components: { List },
声明组件,我们就可以拿着组件去用了!
数据源 :声明了一个名为lists
存储所有数据,名为message
的变量用于添加数据,并且在input
输入框中使用v-model双向动态绑定message
方法 :我们声明了一个submit
方法使用@click
绑定点击事件在确定按钮上,当我们点击确定按钮时,当输入框不为空时,会把数据存储在lists
数组当中。
watch:这里我们用于监听子组件是否修改props中数据,我们在子组件中会添加一个按钮,尝试修改props中的数据,这里用于监听数据是否成功被修改。
如何给子组件传值 : <List :lists = "lists"/>
这里我们直接在组件声明的位置使用v-bind
绑定一个属性将lists数组
传给子组件
接下来再为大家介绍子组件
html
<!-- List.vue -->
<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中的数据
props:['lists'],
//尝试修改数据
methods:{
changeProps(){
this.lists[0] = 'HTML'
}
}
}
</script>
在子组件当中,我们拿到父组件传过来的数据,使用v-for
将数据装载到页面上,并且提供一个修改props的按钮尝试修改父组件中传过来的数据,在父组件中用watch
监听是否成功修改。
拿数据 :这里我们直接使用props:['lists'],
拿到名为lists
的数据,并且使用v-for
在li
中加载数据
尝试修改数据 :我们提供一个按钮,点击就会修改lists
数据中下标为0的数据,将其变为HTML
看看效果!
可以看到,我们子组件不仅读到父组件中传过来的数据,并且也实现了动态更新!当我们点击修改props的时候,页面上第一条数据也由html变为了HTML,但是我们看看父组件中的数据是否也同样更改呢?
我们可以看到,控制台没有任何打印,也就是父组件中的数据并没有受到任何影响,子组件仅仅只是修改父组件已经传过来的数据,修改的是子组件本身拿到的数据,而没有修改父组件中的数据。
这是因为props是单向数据流,子组件只能用,不建议修改,改了了父组件也无法感应到
官方文档描述
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。
$emit 子组件向父组件传值
子组件要如何给父组件传值呢?
** <math xmlns="http://www.w3.org/1998/Math/MathML"> e m i t ∗ ∗ :用于声明由组件触发的自定义事件,在当前组件触发一个自定义事件。任何额外的参数都会传递给事件监听器的回调函数。验证函数会接收到传递给组件的 ' emit**:用于声明由组件触发的自定义事件,在当前组件触发一个自定义事件。任何额外的参数都会传递给事件监听器的回调函数。验证函数会接收到传递给组件的 ` </math>emit∗∗:用于声明由组件触发的自定义事件,在当前组件触发一个自定义事件。任何额外的参数都会传递给事件监听器的回调函数。验证函数会接收到传递给组件的'emit
调用的额外参数。例如,如果
this.$emit('foo', 1)被调用,
foo相应的验证函数将接受参数
1`。验证函数应返回布尔值,以表明事件参数是否通过了验证。用我们自己的话讲:子组件可以通过$emit发布一个事件,且携带事件参数,父组件通过订阅这个事件从而当这个事件触发时,父组件的订阅也就生效了!同时,父组件的订阅也能拿到这个参数。
用法
vue
<!--子组件Head.vue-->
this.$emit('add',this.message)//两个参数 第一个事件名称,第二个是一个值
<!--父组件-->
<!-- 只要子组件发布了add事件,handle事件就触发 -->
<Head @add="handle"/>
拿到底如何使用呢?我们拿着刚刚那个案例,做一些稍稍的修改:
先介绍子组件Head.vue
html
<!-- Head.vue -->
<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) {
// 将message 传给父组件使用
// 发布一个add事件
this.$emit('add',this.message)//两个参数 第一个事件名称,第二个是一个值
this.message = ''
}
},
}
}
</script>
这里,我们通过子组件一个input
输入框,在子组件中输入一个数据,再将这个数据传给父组件,让父组件拿着这数据装载到页面上。
数据源 :在子组件数据源中我们定义了一个message
变量,用v-model
动态绑定input
输入框中输入的数据
方法 :定义了一个submit
方法,在方法体当中,当变量message
不为空时,我们使用$emit
发布一个名为add
的自定义事件,并且接受message
作为一个参数。
确定按钮 :在这个确定按钮上,我们使用v-on
绑定一个点击事件,当点击这个按钮的时候,submit
方法就被触发!
至此,我们子组件的功能就基本上介绍完了,接下来,我们在介绍父组件App.vue
html
<!-- App.vue -->
<template>
<div>
<!-- <head /> -->
<!-- 只要子组件发布了add事件,handle事件就触发 -->
<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>
在App.vue页面中,我们通过定义一个lists
数组,使用v-for
将数组中的数据装载到页面上,并且可以拿到子数组中传过来的数据
数据源 :定义了一个lists
数组,存储数据,并且通过v-for
将lists
中的数据装载到页面上去。
方法 :定义了一个handle
方法,并且接受一个参数val
,将val
存储到lists
数组当中。父组件通过<Head @add="handle"/>
订阅了子组件中的add
方法,只要子组件发布了add
方法也就是add
方法触发时,父组件中的handle
方法就会触发,并且handle
方法中的参数来自于子组件中$emit
中携带的参数。
我们来看看效果如何!
可以看到,子组件向父组件传值的效果我们也实现了!
props 和 $emit借助父组件实现兄弟组件通信
上面我们已经介绍完了知识点,我们接着拿着上面的案例,这次我们有一个父组件App.vue 两个子组件Head.vue和List.vue,数据分别在两个子组件当中,我们要实现兄弟组件通信,我们已经知道了父组件向子组件传值和子组件向父组件传值,那么我们就拿着父组件作为中介,就可以实现兄弟组件的通信了!
html
<!-- 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){
this.msg = val
}
},
}
</script>
html
<!-- Head.vue -->
<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)
{
this.$emit('add',this.message)
this.message = ''
}
}
}
}
</script>
html
<!-- List.vue -->
<template>
<div>
<div class="body">
<ul>
<li v-for="(item, index) in lists" :key="index">{{ item }}</li>
</ul>
</div>
</div>
</template>
<script>
export default {
props: {
msg:{
type:String,
default:''//默认为空
}
},
watch:{
msg(newVal)
{
this.lists.push(newVal)
}
},
data() {
return {
lists: ['html', 'css'],
}
},
}
</script>
三个模块的代码如上,接下来我们来一一分析:
Head.vue
:在Head.vue
中,我们需要提交一个数据message
给到List.vue
,同样的我们通过v-model
双向动态绑定message
,然后使用$emit
发布名为add
的自定义事件,并且携带参数message
App.vue
:在App.vue
中,我们先引入这两个组件,并且将它们装载在页面上,同时给Head.vue
的add
事件添加一个订阅,当add
事件触发的时候,就会执行handle
方法,handle
方法接受事件中的参数,并且赋值给本数据源中的数据msg
(中间变量),在通过props
将这个数据msg
通过:msg:"msg"
传给子数组List
。
List.vue
:在List.vue
中,数据源中声明了一个lists
数组,并且通过v-for
装载到页面上,然后我们通过props:{msg:{type:String,default:''}}
的形式拿到,
js
props: {
msg:{
type:String,
default:''//默认为空
}
},
这也是props
拿去数据一种方式通过对象拿数据msg
是数据名,type
是数据的类型,default
是数据默认为空,紧接着我们通过watch
监听msg
,一旦msg
发生变化,我们就把msg
存储到lists
当中!
这样,我们就实现了兄弟组件的通信!看看效果!
二、Vuex 公共仓库
通过上面的学习,我们已经知道父子组件通信,以及兄弟组件的通信了,假如现在我们有一个三层组件甚至更多,那么我们的传值可以使用上面的方法实现,但是这会相当麻烦!
今天我们就来介绍一个官方封装的公共仓库:vuex
每一个 Vuex 应用的核心就是 store(仓库)。"store"基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
还有一个新版本的pinia ,后面我们会介绍!今天我们先来学习vuex
安装Vuex
npm安装方式
lua
npm install vuex@next --save
yarn安装方式
sql
yarn add vuex@next --save
紧接着我们可以在src目录下新建一个store文件夹,然后在store
中定义一个js的配置文件!
如何读取仓库中的数据
假如我们再拿到之前的那个案例,现在我们直接通过一个公共的仓库来实现效果!
首先,我们来写一下配置文件!将数据放入到仓库当中,这里我们的配置文件就写成index.js
index.js
js
import { createStore } from 'vuex'
// 创建一个新的store实例
const store = createStore({
state(){//等同于数据源data
return {
lists:['html','css','js']
}
},
})
// 抛出store
export default store
vuex的配置文件和路由十分的类似,首先我们从vuex
中引入一个createStore
函数,我们再定义store
为createStore
的执行结果!创建一个新的store
实例,再使用export default store
抛出这个store
实例。
我们在createStore
中放入一个对象,再声明一个state函数
仓库数据源state :与我们各个模块中的数据源类似,都是一个函数返回一个对象,这里我们直接定义一个数组lists
放入到仓库数据源中。
接着我们来到List.vue
中
html
<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 {
// 计算属性中放入,mapState会自动返回一个值
computed:mapState(['lists'])
}
</script>
我们在list.vue
可以通过引入官方为我们封装方法mapState
读取到仓库数据源中的值!我们要动态得读取仓库中的数据,所有我们用了一个computed
计算属性调用mapState(['lists'])
从中拿到想要的数据,['lists']
就是我们想拿的仓库中的数据。
这里为什么直接调用就可以了,
computed
计算属性不是要一个返回值吗?这是因为
mapState
是自动的返回一个值。
如何修改仓库中数据
有一点我们要知道Vuex
中的数据组件一般是无法修改的,这里需要我们使用一点特殊的手段,也就是仓库中的数据只能由仓库管理员进行修改,其他人想要修改必须通过登记的手段进行修改,不能任意修改。
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
我们在来到仓库的配置文件index.js,我们需要一个往仓库中添加数据的方法。
index.js
js
import { createStore } from 'vuex'
// 创建一个新的store实例
const store = createStore({
state(){//等同于数据源data
return {
lists:['html','css','js']
}
},
// 仓库里面的数据只能仓库管理员动
mutations:{//methods
// 不能用this去访问数据要接收一个形参,第一个参数是内定,第二个参数可以是人为内定的
listsAdd(state,val){
state.lists.push(val)
}
}
})
// 抛出store
export default store
在之前配置的基础上,我们又添加了一个mutations
属性,也就相当于我们之前定义的方法!
**mutations:**在mutations
中的函数不能用this.
去访问仓库数据源中的数据,他默认会接受一个参数,也就是第一个参数为state
,指代的就是仓库数据源,你传入的形参会从第二个参数开始。
为了实现往仓库数据源添加数据的操作,我们添加了一个listsAdd
方法,接收两个参数state和val
,
并且添加到仓库数据源当值state.lists.push(val)
接下来,我们去Head.vue
组件当中
Head.vue
html
<template>
<div>
<div class="head">
<input type="text" v-model="message">
<button @click="submit">确定</button>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
export default {
data() {
return {
message: ''
}
},
methods: {
submit() {
// 如何把数据放进store
this.listsAdd(this.message)//传的实参一定从第二个形参开始
},
// 通过解构拿到mapMutations中的方法
...mapMutations(['listsAdd'])
},
}
</script>
我们要把Head.vue
中的数据添加到仓库当中,我们就要拿到仓库给我们提供的方法!
mapMutations
:官方为我们提供一个拿到mutations
中方法的函数,mapMutations
,我们从vuex
中引入它,然后定义一个methods
,通过解构拿到mapMutations
中的方法...mapMutations(['listsAdd'])
,其中[listsAdd]
就是我们要拿到的方法名,最后,我们在submit
方法中调用这个方法 this.listsAdd(this.message)
这样我们就能message
数据传入到仓库,并且添加到仓库当中了!
最后,我们在App.vue
装载组件再看看效果吧!
App.vue
html
<template>
<div>
<Head />
<List />
</div>
</template>
<script>
import Head from './components/body4/Head.vue'
import List from './components/body4/List.vue'
export default {
components: {
Head,
List
},
data() {
return {
}
},
methods: {
},
}
</script>
效果:
这样,我们父子组件,兄弟组件之间的数据交流就简单多了!当然vuex
的知识也远不如此,我们以后也会进行学习!
最后
各位老铁都看到这里了!coding不易,给我点上一个小小的赞吧!
后续我还会大家持续输出vue3,Element-ui以及相关后端的文章!让我们一起跨步向前!
那么,我们今天就到这啦!
个人Github:一个修远君的库 (github.com)