[Vue3] vue组件通信 props和$emit 以及Vuex基础

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-forli中加载数据

尝试修改数据 :我们提供一个按钮,点击就会修改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-forlists中的数据装载到页面上去。

方法 :定义了一个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.vueadd事件添加一个订阅,当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 和单纯的全局对象有以下两点不同:

  1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 你不能直接改变 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函数,我们再定义storecreateStore的执行结果!创建一个新的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)

相关推荐
Myli_ing2 分钟前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维19 分钟前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
雯0609~40 分钟前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ43 分钟前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z1 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript
星星会笑滴1 小时前
vue+node+Express+xlsx+emements-plus实现导入excel,并且将数据保存到数据库
vue.js·excel·express
彭世瑜1 小时前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4041 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish1 小时前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five1 小时前
TypeScript项目中Axios的封装
开发语言·前端·javascript