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

1,321 阅读4分钟

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

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

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

准备工作

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

vue create component-communicate
// 后面的选项记得勾选vuex

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

GIF 2023-12-21-星期四 0-36-12.gif

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因父组件的更新而变化,自然地向下流下组件……且看文档介绍

1.png

子组件只能用props的值,不建议修改,因为数据流会变得很混乱。改props可以行得通,但是父组件无法监听到,不是双向绑定

App.vue父组件绑定属性用于传值

绑定属性之前你需要将List子组件导入进来

导入一个组件的三个步骤

导入一个组件有三个步骤

  1. import路径引入
  2. components中声明
  3. 在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打造的,它是个仓库,可以被任何一个组件使用

安装

2.png

这里我们直接用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…

求赞.jpg