一网打尽Vue组件通信

779 阅读7分钟

1626922103(1).jpg

作为Vue的核心,组件化为我们节省了大量的开发工作。页面作为组件的容器,在里面我们可以随意的嵌套,组合使用组件。但每个组件都是一个封闭的空间,在一个页面中,我们常常需要做到各组件之间的联动,这个时候我们就要进行组件之间的通信。我们通常根据组件之间的关系来使用不同的通信方式,如上图所示,组件之间的关系通常有如下几种:

父子组件通信

如图中所示的父组件A和子组件B,C的关系,就是父子组件关系,也是我们开发中最常遇到的组件关系。这种情况下我们可以用以下方法进行通信:

1. props/$emit

父组件通过 v-bind:key="xxx" / :key="xxx"的方式向子组件传参,子组件用props接收传入的参数。子组件通过$emit向父组件传递事件,父组件则通过 v-on:event="myEvent" / @event="myEvent" 来监听子组件的事件。下面我们通过一个例子来展示具体的使用:

//父组件  Parent.vue
<template>
  <div id="app">
    <children :names="names" @submit="submit"></children>
  </div>
</template>
<script>
import children from "./components/children"
export default {
  components:{
    "users":Users
  },
  data(){
    return{
      names:["Henry","Bucky","Emily"]
    }
  },
  methods: {
    submit(data) {
      console.log(data)
    }
  }
}
//children子组件
<template>
  <div class="hello">
    <ul>
      <li v-for="name in names">{{name}}</li>//遍历传递过来的值,然后呈现到页面
    </ul>
    <button @click="submitCount"></button>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    names:{           //这个就是父组件中子标签自定义名字
      type:Array,
      required:true
    }
  },
  methods: {
    submitCount() {
      this.$emit('submit', names.length) //此外submit为父组件中子标签处监听的事件名
    }
  }
}
</script>

2.$parent / $children与 ref

  • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
  • $parent / $children:访问父 / 子实例 这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 ref来访问组件的例子:
// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}
// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js  访问对应组件实例的数据
      comA.sayHello();  // 弹窗  调用对应组件实例的方法
    }
  }
</script>

$children 可以用来在父组件中访问子组件,但需要注意的是, this.$children拿到的是一个组件实例的数组,而且是无序的,也不是响应式的。这样导致我们无法明确的找到指定的子组件,所以不推荐使用。 $parent 当前组件树的一级父实例。如果当前实例没有父实例,此实例将会是其自己。

兄弟组件通信

图中的子组件B和子组件C就是我们说的兄弟组件,它们有一个共同的父组件。因为有了这个共同父组件桥梁的存在,我们可以通过上面介绍的 props/$emit$ref组合来实现兄弟组件间的通信:

1626938403(1).jpg

//子组件B
this.$emit('submit', data)  //通过$emit通知到父组件
//父组件A
<children-b @submit="submit"></children-b> //监听B组件的提交事件
<children-c ref="child_c"></children-c> 
...
methods: {
    submit(data) {
      this.$refs.child_c.acceptBmsg(data)  //通过refs拿到子组件C实例,直接调用C组件方法接收从B传过的数据
    }
}
//子组件C
methods: {
    //用于接收B组件传来的数据
    acceptBmsg(data) {
      console.log(data)
    }
}

虽然这样也可以实现兄弟组件间的通信,但毕竟过了一道父组件的转发,需要在父组件中添加一些转发的逻辑代码。如果项目中这种情况较多的情况下,我们就需要在每个父组件中都得写转发逻辑,这样会导致代码混乱。这时我们就可以通过EventBus(又叫:事件总线)来统一管理。

3.EventBus

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、隔代、陌生组件。这里其实有点像发布,订阅者模式。可以做到一个组件发布,多个组件订阅;多个组件发布,一个组件订阅;多个组件发布,多个组件订阅。实现如下:

1626942097(1).jpg

在目录下新建bus.js文件,新建并输出一个空的Vue实例

//bus.js
import Vue from 'vue'

const bus = new Vue();
export default bus

场景: A组件发信息给B组件

//A组件
<template>
  <div>
    <button @click="send">将数据发送给B组件</button>
  </div>
</template>
<script>
import { EventBus } from './bus.js'
export default {
    data(){
        return {
            name: 'LiLei'
        }
    },
    methods: {
        send(){
            EventBus.$emit('sengMsg', this.name)
        }
    }
}
</script>

//B组件
<script>
import { EventBus } from './bus.js'
export default {
    mounted(){
        EventBus.$on('sengMsg',(data)=>{
            console.log(data)
        })
    }
}
</script>

袓孙(隔代)组件通信

在我们的实际开发中,还会遇到另外一种情况即多层嵌套的组件,如开头图中的组件A和组件D的关系,通常情况下,组件向组件传递数据,可以采用父子props层层传递,也可以使用EventBus直接交互。在Vue2.2.0之后,Vue还提供了provide/inject选项。

这对选项允许一个祖先组件向其所有子孙后代组件注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。 也就是说,在父组件只要声明了provide,在其子组件,孙组件,曾孙组件等能形成上下游关系的组件中交互,无论多深都能通过inject来访问provider中的数据。而不是局限于只能从当前父组件的prop属性来获取。注意他只做祖先通后代的单向传递的一个办法。有人这么形容:

provide就相当于加强版父组件prop,可以跨越中间组件inject就相当于加强版子组件的props

使用方法:

  • provide 提供变量: Object | () => Object
  • inject 注入变量: Array<string> | { [key: string]: string | Symbol | Object }

  provide 选项应该是一个对象或返回一个对象的函数。 该对象包含可注入其子孙的属性。在该对象中,它支持ES6中Symbol作为key,但只在原生支持等环境下可工作。

  inject 选项可以是

  • 一个字符串数组

  • 一个对象,key为本地绑定名,value为:

    • 在可用的注入内容中搜索用的key,或
    • 一个对象,其 from 属性是在可用的注入内容中搜索用的keydefault属性是降级情况下使用的value

提示:provideinject绑定并不是可响应的,这是刻意为之,然而如果你传入了一个可监听的对象,那么该对象的属性还是可监听的。(使用中我们常传入整个组件实例)

可以使用Vue 2.6最新API Vue.observable 优化响应式 provide(推荐)

//父组件
<template>
  <div>
    <p>{{ title }}</p >
    <son></son>
  </div>
</template>
<script>
  import Son from "./son"
  export default {
    name: 'Father',
    components: { Son },
    // provide选项提供变量
    provide: {
      message: 'provided by father'
    },
    data () {
      return {
        title: '父组件'
      }
    },
    methods: { ... }
  }
</script>

我们在子组件中不做任何处理

//子组件
<template>
  <div>
    <p>{{ title }}</p >
    <grand-son></grand-son>
  </div>
</template>
<script>
import grandSon from "./grandSon "
export default {
  name: "Son",
  components: { grandSon },
  data () {
    return {
      title: '子组件'
    }
  },
};
</script>

在孙子组件中,我们通过inject来接收父组件中provide中的数据

//孙子组件
<template>
  <div>
    <p>message:{{ message }}</p >
  </div>
</template>
<script>
export default {
  name: "GrandSon",
  inject: [ "message" ],
  data () {
    return {
      title: '孙组件'
    }
  },
  methods: { ... }
};
</script>

陌生组件通信

上面所有的情况都是有血缘关系的组件通信,那对于毫无血缘关系的组件,我们该采用哪种通信方式呢?其实上面提到的EventBus已经可以满足陌生组件间的通信。但同时Vue也提供了更加强大的Vuex(状态管理模式)来实现多个组件的状态共享。

Vuex成员列表:

  • state 存放状态
  • mutations state成员操作
  • getters 加工state成员给外界
  • actions 异步操作
  • modules 模块化状态管理

1627007846(1).jpg

简要介绍各模块在流程中的功能:

  • Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
  • state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
  • getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。
  • dispatch:操作行为触发方法,是唯一能执行action的方法。
  • actions:操作行为处理模块,由组件中的$store.dispatch('action 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
  • commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
  • mutations:状态改变操作方法,由actions中的commit('mutation 名称')来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。

如果想系统地学习Vuex的使用,请参考:# Vuex 是什么

总结:

我们上面介绍了多种组件间通信的方式,但除了学会如何使用,更重要的是我们要明白什么场景下适合用哪种通信方式。如果你的项目只是一个简单的应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式 (opens new window)就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。条条大路通罗马,但最重要的是你要找到那条最近,最省力的那条。

莎士比亚说过, 看完不点赞的同志,不是好同志

可能感兴趣的文章: