vue组件通信【父子,子父,vuex】

vue组件通信方式也是面试热点问题,这个东西非常的重要,如果不懂通信那项目就无法开展下去,今天我们就来一起认识下vue组件的最基本的三种方式

  1. 父子组件通信:父组件v-bind绑定属性用于传值,子组件props接收
  2. 子父组件通信:父组件订阅一个事件,子组件通过$emit发布该事件,且携带事件参数,让父组件的订阅生效
  3. 兄弟组件通信:vuex

为了方法讲解,我们引入一个简单的栗子----todolist

准备工作

利用cli脚手架创建一个项目

arduino 复制代码
vue create component-communicate
// 后面的选项记得勾选vuex

我们在App.vue中简单实现一个todolist(没写css,主讲原理

App.vue

xml 复制代码
<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这个配置文件去加一行代码

vbnet 复制代码
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就是父组件传过来的值

xml 复制代码
<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子组件导入进来

导入一个组件的三个步骤

导入一个组件有三个步骤

  1. import路径引入
  2. components中声明
  3. 在template中使用,这里记得绑定属性

App.vue

xml 复制代码
<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

xml 复制代码
<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

xml 复制代码
<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

xml 复制代码
<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

xml 复制代码
<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

xml 复制代码
<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

javascript 复制代码
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掉,简直跟路由一模一样的

javascript 复制代码
import store from './store'

createApp(App).use(store).mount('#app')

此时的App.vue写法只需如下

App.vue

xml 复制代码
<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

xml 复制代码
<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

xml 复制代码
<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...

相关推荐
mldong1 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排2 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰4 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen6 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马7 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu7 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss