[Vue] vue组件通信 props和$emit 以及Vuex基础
Vue组件通信
今天,我们就来带大家学习一下vue中的组件通信!注意,这个是我们在面试当中必考的内容!
vue组件通信有非常多的方式!
一、props 和 $emit
props 父组件向子组件传值
父组件要如何向子组件传一个值呢?这里,我们就要介绍一下props
props属性:在Vue.js中,
props是一种用于从父组件向子组件传递数据的机制。通过props,父组件可以向子组件传递数据,使得子组件能够接收并使用这些数据。
我们可以使用v-bind在组件上绑定一个属性就可以将父组件中的数据传到子组件当中。
<!-- 我们可以使用v-bind绑定属性将数据传给子组件 "lists"代表的就是数据源中的lists数据 -->
<List :lists = "lists"/>
子组件又如何拿到父组件传过来的数据呢?
在上篇文章我们已经介绍到defineProps API
defineProps API :在Vue 3中,
defineProps函数是用于在函数式组件中声明和获取 props 的工具。所以我们可以在子组件中导入defineProps 函数,通过const {users} = defineProps(['users'])过解构赋值来获取users。也就是数据,
// defineProps api 获取父组件传过来的参数
import { defineProps } from 'vue';
const {users} = defineProps(['users'])//users父组件传过来的数据的名称
我们再介绍一种简单的语法:
props:['lists']//lists是父组件传过来数据的名称,这里不仅可以是数组也可以是对象
具体如何使用,我们来看一个案例:
<!-- 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数组传给子组件
接下来再为大家介绍子组件
<!-- 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
看看效果!
![[Vue] vue组件通信 props和$emit 以及Vuex基础](https://static.blogweb.cn/article/a272853bf5b04909870f217d4dbb7558.webp)
可以看到,我们子组件不仅读到父组件中传过来的数据,并且也实现了动态更新!当我们点击修改props的时候,页面上第一条数据也由html变为了HTML,但是我们看看父组件中的数据是否也同样更改呢?
![[Vue] vue组件通信 props和$emit 以及Vuex基础](https://static.blogweb.cn/article/5d68c69b21f94c9ebf42653ca59ec0aa.webp)
我们可以看到,控制台没有任何打印,也就是父组件中的数据并没有受到任何影响,子组件仅仅只是修改父组件已经传过来的数据,修改的是子组件本身拿到的数据,而没有修改父组件中的数据。
这是因为props是单向数据流,子组件只能用,不建议修改,改了了父组件也无法感应到
官方文档描述
所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。
$emit 子组件向父组件传值
子组件要如何给父组件传值呢?
$emit:用于声明由组件触发的自定义事件,在当前组件触发一个自定义事件。 任何额外的参数都会传递给事件监听器的回调函数。验证函数会接收到传递给组件的$emit调用的额外参数。例如,如果this.$emit('foo', 1)被调用,foo相应的验证函数将接受参数1。验证函数应返回布尔值,以表明事件参数是否通过了验证。 用我们自己的话讲:子组件可以通过$emit发布一个事件,且携带事件参数,父组件通过订阅这个事件从而当这个事件触发时,父组件的订阅也就生效了!同时,父组件的订阅也能拿到这个参数。
用法
<!--子组件Head.vue-->
this.$emit('add',this.message)//两个参数 第一个事件名称,第二个是一个值
<!--父组件-->
<!-- 只要子组件发布了add事件,handle事件就触发 -->
<Head @add="handle"/>
拿到底如何使用呢?我们拿着刚刚那个案例,做一些稍稍的修改:
先介绍子组件Head.vue
<!-- 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
<!-- 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中携带的参数。
我们来看看效果如何!
![[Vue] vue组件通信 props和$emit 以及Vuex基础](https://static.blogweb.cn/article/be207eafab8841eea5aba88b3d34c7d4.webp)
可以看到,子组件向父组件传值的效果我们也实现了!
props 和 $emit借助父组件实现兄弟组件通信
上面我们已经介绍完了知识点,我们接着拿着上面的案例,这次我们有一个父组件App.vue 两个子组件Head.vue和List.vue,数据分别在两个子组件当中,我们要实现兄弟组件通信,我们已经知道了父组件向子组件传值和子组件向父组件传值,那么我们就拿着父组件作为中介,就可以实现兄弟组件的通信了!
<!-- 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>
<!-- 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>
<!-- 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:''}}的形式拿到,
props: {
msg:{
type:String,
default:''//默认为空
}
},
这也是props拿去数据一种方式通过对象拿数据msg是数据名,type是数据的类型,default是数据默认为空,紧接着我们通过watch监听msg,一旦msg发生变化,我们就把msg存储到lists当中!
这样,我们就实现了兄弟组件的通信!看看效果!
![[Vue] vue组件通信 props和$emit 以及Vuex基础](https://static.blogweb.cn/article/c0a1bafe0a5e4903893f756045ba339c.webp)
二、Vuex 公共仓库
通过上面的学习,我们已经知道父子组件通信,以及兄弟组件的通信了,假如现在我们有一个三层组件甚至更多,那么我们的传值可以使用上面的方法实现,但是这会相当麻烦!
今天我们就来介绍一个官方封装的公共仓库:vuex
每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
- Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
- 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
还有一个新版本的pinia,后面我们会介绍!今天我们先来学习vuex
安装Vuex
npm安装方式
npm install vuex@next --save
yarn安装方式
yarn add vuex@next --save
紧接着我们可以在src目录下新建一个store文件夹,然后在store中定义一个js的配置文件!
如何读取仓库中的数据
假如我们再拿到之前的那个案例,现在我们直接通过一个公共的仓库来实现效果!
首先,我们来写一下配置文件!将数据放入到仓库当中,这里我们的配置文件就写成index.js
index.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中
<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
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
<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
<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>
效果:
![[Vue] vue组件通信 props和$emit 以及Vuex基础](https://static.blogweb.cn/article/b0989fa0513e45a698b83a35ed150f61.webp)
这样,我们父子组件,兄弟组件之间的数据交流就简单多了!当然vuex的知识也远不如此,我们以后也会进行学习!
最后
各位老铁都看到这里了!coding不易,给我点上一个小小的赞吧!
后续我还会大家持续输出vue3,Element-ui以及相关后端的文章!让我们一起跨步向前!
![[Vue] vue组件通信 props和$emit 以及Vuex基础](https://static.blogweb.cn/article/2104e5a4002a4e43b05125cd0d852ef1.webp)
那么,我们今天就到这啦!
个人Github:一个修远君的库 (github.com)
转载自:https://juejin.cn/post/7322518833817681930