vue中组件间通信的6种方式

Posted Jiang_JY

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue中组件间通信的6种方式相关的知识,希望对你有一定的参考价值。

前言

组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互进行直接的引用,所以组件间的相互通信是非常重要的。

除了使用vuex外还有下面6种组件间的通信方式:

1、 props / $emit

父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信

(1)父组件向子组件传值(props的用法)

props的特点:

  • props只能是父组件向子组件进行传值,props使得父子组件之间形成一个单向的下行绑定。子组件的数据会随着父组件的更新而响应式更新。
  • props可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以是传递一个函数。
  • props属性名规则:若在props中使用驼峰形式,模板中标签需要使用短横线的形式来书写。

用法:

父组件:

// 父组件
<template>
    <div id="father">
        <son :msg="msgData" :fn="myFunction"></son>
    </div>
</template>

<script>
import son from "./son.vue";
export default 
    name: father,
    data() 
        msgData: "父组件数据";
    ,
    methods: 
        myFunction() 
            console.log("vue");
        
    ,
    components: 
        son
    
;
</script>

子组件:

// 子组件
<template>
    <div id="son">
        <p>msg</p>
        <button @click="fn">按钮</button>
    </div>
</template>
<script>
export default 
    name: "son",
    props: ["msg", "fn"]
;
</script>

(2)子组件向父组件传递数据($emit的用法)

$emit的特点:

  • $emit 绑定一个自定义事件,当这个事件被执行的时候就会将参数传递给父组件,而父组件通过v-on监听并接收参数

用法:

父组件:

// 父组件
<template>
  <div class="section">
    <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
    <p>currentIndex</p>
  </div>
</template>

<script>
import comArticle from './test/article.vue'
export default 
  name: 'comArticle',
  components:  comArticle ,
  data() 
    return 
      currentIndex: -1,
      articleList: ['红楼梦', '西游记', '三国演义']
    
  ,
  methods: 
    onEmitIndex(idx) 
      this.currentIndex = idx
    
  

</script>

子组件:

//子组件
<template>
  <div>
    <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">item</div>
  </div>
</template>

<script>
export default 
  props: ['articles'],
  methods: 
    emitIndex(index) 
      this.$emit('onEmitIndex', index) // 触发父组件的方法,并传递参数index
    
  

</script>

2、ref / $refs

这种方式也是实现父子组件之间的通信

ref:这个属性用在子组件上,它的用用就指向了子组件的实例,可以通过实例来访问组件的数据和方法

用法:

在子组件中:

export default 
  data () 
    return 
      name: 'javascript'
    
  ,
  methods: 
    sayHello () 
      console.log('hello')
    
  

在父组件中:

<template>
  <child ref="child"></component-a>
</template>
<script>
  import child from './child.vue'
  export default 
    components:  child ,
    mounted () 
      console.log(this.$refs.child.name);  // JavaScript
      this.$refs.child.sayHello();  // hello
    
  
</script>

3、eventBus事件总线($emit / $on)

eventBus事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下:

(1)创建事件中心管理组件之间的通信

// event-bus.js

import Vue from 'vue'
export const EventBus = new Vue()

(2)发送事件 假设有两个兄弟组件firstCom和secondCom:

firstCom和secondCom的父组件:

<template>
  <div>
    <first-com></first-com>
    <second-com></second-com>
  </div>
</template>

<script>
import firstCom from './firstCom.vue'
import secondCom from './secondCom.vue'
export default 
  components:  firstCom, secondCom 

</script>

在firstCom组件中发送事件:

<template>
  <div>
    <button @click="add">加法</button>    
  </div>
</template>

<script>
import EventBus from './event-bus.js' // 引入事件中心

export default 
  data()
    return
      num:0
    
  ,
  methods:
    add()
      EventBus.$emit('addition', 
        num:this.num++
      )
    
  

</script>

(3)接收事件

在secondCom组件中接收事件:

<template>
  <div>求和: count</div>
</template>

<script>
import  EventBus  from './event-bus.js'
export default 
  data() 
    return 
      count: 0
    
  ,
  mounted() 
    EventBus.$on('addition', param => 
      this.count = this.count + param.num;
    )
  

</script>

在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。

4、依赖注入(provide / inject)

这种方式就是vue中依赖注入,该方法用于 父子组件之间 的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方式来进行传值。就不用一层一层的传递数据了。

provide和inject是vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。

  • provide 钩子用来发送数据或方法。
  • inject钩子用来接收数据或方法

用法:

父组件中:

provide()  
    return      
        num: this.num  
    ;

子组件中:

inject: ['num']

还有另一种写法,这种写法可以访问父组件中的所有属性:

provide() 
 return 
    app: this
  ;

data() 
 return 
    num: 1
  ;


inject: ['app']
console.log(this.app.num)

注意: 依赖注入所提供的属性是非响应式的

5、$parent / $children

  • 使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)。
  • 使用 $children 可以让组件访问子组件的实例,但是, $children 并不能保证顺序,并且访问的数据也不是响应式的。

用法:

子组件中:

<template>
  <div>
    <span>message</span>
    <p>获取父组件的值为:  parentVal</p>
  </div>
</template>

<script>
export default 
  data() 
    return 
      message: 'Vue'
    
  ,
  computed:
    parentVal()
      return this.$parent.msg;
    
  

</script>

父组件中:

<template>
  <div class="hello_world">
    <div>msg</div>
    <child></child>
    <button @click="change">点击改变子组件值</button>
  </div>
</template>

<script>
import child from './child.vue'
export default 
  components:  child ,
  data() 
    return 
      msg: 'Welcome'
    
  ,
  methods: 
    change() 
      // 获取到子组件
      this.$children[0].message = 'JavaScript'
    
  

</script>

在上面的代码中,子组件获取到了父组件的parentVal值,父组件改变了子组件中message的值。

注意

  • 通过 $parent 访问到的是上一级父组件的实例,可以使用 $root 来访问根组件的实例
  • 在组件中使用$children拿到的是所有的子组件的实例,它是一个数组,并且是无序的
  • 在根组件 #app 上拿 $parent 得到的是 new Vue()的实例,在这实例上再拿 $parent 得到的是undefined,而在最底层的子组件拿 $children 是个空数组
  • $children 的值是数组,而 $parent是个对象

6、$attrs / $listeners

考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给C组件传递数据,这种隔代传数据的情况该使用哪种方式呢?

如果是用props/ $emit 来一级一级的传递,确实可以完成,但是比较复杂;如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;如果使用vuex,如果仅仅是传递数据,那可能有点浪费了。

针对上述情况,vue引入了 $attrs / $listeners,实组件之间的跨代通信。

  • $attrs:继承所有的父组件属性(除了props传递的属性、class 和 style),一般用在子组件的子元素上
  • $listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on=" $listeners " 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)

再说一下 inheritAttrs

  • 默认值为true,继承所有的父组件属性除props之外的所有属性。
  • 只继承class属性。

$attrs / $listeners的用法:
A组件(APP.vue):

<template>
    <div id="app">
        //此处监听了两个事件,可以在B组件或者C组件中直接触发 
        <child1 :p-child1="child1" :p-child2="child2" @test1="onTest1" @test2="onTest2"></child1>
    </div>
</template>
<script>
import Child1 from './Child1.vue';
export default 
    components:  Child1 ,
    methods: 
        onTest1() 
            console.log('test1 running');
        ,
        onTest2() 
            console.log('test2 running');
        
    
;
</script>

B组件(Child1.vue):

<template>
    <div class="child-1">
        <p>props: pChild1</p>
        <p>$attrs: $attrs</p>
        <child2 v-bind="$attrs" v-on="$listeners"></child2>
    </div>
</template>
<script>
import Child2 from './Child2.vue';
export default 
    props: ['pChild1'],
    components:  Child2 ,
    inheritAttrs: false,
    mounted() 
        this.$emit('test1'); // 触发APP.vue中的test1方法
    
;
</script>

C 组件 (Child2.vue):

<template>
    <div class="child-2">
        <p>props: pChild2</p>
        <p>$attrs: $attrs</p>
    </div>
</template>
<script>
export default 
    props: ['pChild2'],
    inheritAttrs: false,
    mounted() 
        this.$emit('test2');// 触发APP.vue中的test2方法
    
;
</script>

在上述代码中:

  • C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性
  • 在B组件中通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)

总结

根据以上对这6种组件间的通信方法,可以将不同组件间的通信分为4种类型:父子组件间通信、跨代组件间通信、兄弟组件间通信、任意组件间通信

1、父子组件间通信

  • 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过 emit 触发事件来向父组件发送数据。
  • 通过 ref 属性给子组件设置一个名字。父组件通过 $refs 组件名来获得子组件,子组件通过 $parent 获得父组件,这样也可以实现通信。
  • 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。

2、跨代组件间通信

  • 跨代组件间通信其实就是多层的父子组件通信,同样可以使用上述父子组件间通信的方法,只不过需要多层通信会比较麻烦。
  • 使用上述的6种方法的$attrs / $listeners方法。

3、兄弟组件间通信

  • 通过 $parent + $refs 以父组件为中间人来获取到兄弟组件,也可以进行通信。

4、任意组件间通信

  • 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。

如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

Vue 组件间的通信方式

前言

在Vue组件库的开发过程中,组件之间的通信一直是一个重要的课题。虽然官方的Vuex状态管理方案可以很好的解决组件之间的通信问题,但是组件库内部对Vuex的使用往往比较繁重。本文列举了几种实用的不使用Vuex的组件间通信方法,供大家参考。

组件之间通信的场景

在进入我们今天的主题之前,我们先来总结下 Vue 组件之间通信的几种场景,一般可以分为如下几种场景:

  1. 父子组件之间的通信
  2. 兄弟组件之间的通信
  3. 隔代组件之间的通信

父子组件之间的通信

父子组件之间的通信应该是 Vue 组件通信中最简单也最常见的一种了,概括为两个部分:父组件通过 prop 向子组件传递数据,子组件通过自定义事件向父组件传递数据。

父组件通过 prop 向子组件传递数据

Vue 组件的数据流向都遵循单向数据流的原则,所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

父组件 ComponentA:

<template>
<div>
<component-b title="welcome"></component-b>
</div>
</template>
<script>
import ComponentB from ./ComponentB

export default
name: ComponentA,
components:
ComponentB


</script>
复制代码

子组件 ComponentB:

<template>
<div>
<div>title</div>
</div>
</template>
<script>
export default
name: ComponentB,
props:
title:
type: String,



</script>
复制代码

子组件通过自定义事件向父组件传递数据

在子组件中可以通过 ​​$emit​​ 向父组件发生一个事件,在父组件中通过 ​​v-on​​/​​@​​ 进行监听。

子组件 ComponentA:

<template>
<div>
<component-b :title="title" @title-change="titleChange"></component-b>
</div>
</template>
<script>
import ComponentB from ./ComponentB

export default
name: ComponentA,
components:
ComponentB
,
data:
title: Click me
,
methods:
titleChange(newTitle)
this.title = newTitle



</script>
复制代码

子组件 ComponentB:

<template>
<div>
<div @click="handleClick">title</div>
</div>
</template>
<script>
export default
name: ComponentB,
props:
title:
type: String,

,
methods:
handleClick()
this.$emit(title-change, New title !)



</script>
复制代码

这个例子非常简单,在子组件 ComponentB 里面通过 ​​$emit​​ 派发一个事件 ​​title-change​​,在父组件 ComponentA 通过 ​​@title-change​​ 绑定的 ​​titleChange​​ 事件进行监听,ComponentB 向 ComponentA 传递的数据在 ​​titleChange​​ 函数的传参中可以获取到。

兄弟组件之间的通信

状态提升

写过 React 的同学应该对组件的 ​​状态提升​​ 概念并不陌生,React 里面将组件按照职责的不同划分为两类:​​展示型组件(Presentational Component)​​ 和 ​​容器型组件(Container Component)​​。

展示型组件不关心组件使用的数据是如何获取的,以及组件数据应该如何修改,它只需要知道有了这些数据后,组件 UI 是什么样子的即可。外部组件通过 props 传递给展示型组件所需的数据和修改这些数据的回调函数,展示型组件只是它们的使用者。

容器型组件的职责是获取数据以及这些数据的处理逻辑,并把数据和逻辑通过 props 提供给子组件使用。

因此,参考 React 组件中的 ​​状态提升​​ 的概念,我们在两个兄弟组件之上提供一个父组件,相当于容器组件,负责处理数据,兄弟组件通过 props 接收参数以及回调函数,相当于展示组件,来解决兄弟组件之间的通信问题。

ComponentA (兄弟组件 A):

<template>
<div>
<div>title</div>
<div @click="changeTitle">click me</div>
</div>
</template>
<script>
export default
name: ComponentA,
props:
title:
type: String
,
changeTitle: Function


</script>
复制代码

ComponentB (兄弟组件 B):

<template>
<div>
<div>title</div>
<div @click="changeTitle">click me</div>
</div>
</template>
<script>
export default
name: ComponentB,
props:
title:
type: String
,
changeTitle: Function


</script>
复制代码

ComponentC (容器组件 C):

<template>
<div>
<component-a :title="titleA" :change-title="titleAChange"></component-a>
<component-b :title="titleB" :change-title="titleBChange"></component-b>
</div>
</template>
<script>
import ComponentA from ./ComponentA
import ComponentB from ./ComponentB

export default
name: ComponentC,
components:
ComponentA,
ComponentB
,
data:
titleA: this is title A,
titleB: this is title B
,
methods:
titleAChange()
this.titleA = change title A
,
titleBChange()
this.titleB = change title B



</script>
复制代码

可以看到,上述这种 "状态提升" 的方式是比较繁琐的,特别是兄弟组件的通信还要借助于父组件,组件复杂之后处理起来是相当麻烦的。

隔代组件之间的通信

隔代组件之间的通信可以通过如下几种方式实现:

  • ​$attrs​​/​​$listeners​
  • ​rovide​​/​​inject​
  • 基于​​$parent​​/​​$children​​ 实现的​​dispatch​​ 和​​broadcast​

attrs/attrs/attrs/listeners

Vue 2.4.0 版本新增了 ​​$attrs​​ 和 ​​$listeners​​ 两个方法。先看下官方对 ​​$attrs​​ 的介绍:

包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (​​class​​ 和 ​​style​​ 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (​​class​​ 和 ​​style​​ 除外),并且可以通过 ​​v-bind="$attrs"​​ 传入内部组件 —— 在创建高级别的组件时非常有用。

看个例子:

组件 A (ComponentA):

<template>
<component-a name="Lin" age="24" sex="male"></component-a>
</template>
<script>
import ComponentB from @/components/ComponentB.vue

export default
name: App,
components:
ComponentA


</script>
复制代码

组件 B (ComponetB):

<template>
<div>
I am component B
<component-c v-bind="$attrs"></component-c>
</div>
</template>
<script>
import ComponentC from @/components/ComponentC.vue

export default
name: ComponentB,
inheritAttrs: false,
components:
ComponentC


</script>
复制代码

组件 C (ComponetC):

<template>
<div>
I am component C
</div>
</template>
<script>

export default
name: ComponentC,
props:
name:
type: String

,
mounted: function()
console.log($attrs, this.$attrs)


</script>
复制代码

这里有三个组件,祖先组件 (ComponentA)、父组件 (ComponentB) 和子组件 (ComponentC)。这三个组件构成了一个典型的子孙组件之间的关系。

ComponetA 给 ComponetB 传递了三个属性 name、age 和 sex,ComponentB 通过 ​​v-bind="$attrs"​​ 将这三个属性再透传给 ComponentC, 最后在 ComponentC 中打印 ​​$attrs​​ 的值为:

age: 24, sex: male
复制代码

为什么我们一开始传递了三个属性,最后只打印了两个属性 age 和 sex 呢?因为在 ComponentC 的 props 中声明了 name 属性,​​$attrs​​ 会自动排除掉在 props 中声明的属性,并将其他属性以对象的形式输出。

说白了就是一句话,​​$attrs​​ 可以获取父组件中绑定的非 Props 属性

一般在使用的时候会同时和 ​​inheritAttrs​​ 属性配合使用。

如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 ​​inheritAttrs: false​​。

在 ComponentB 添加了 ​​inheritAttrs=false​​ 属性后,ComponentB 的 dom 结构中可以看到是不会继承父组件传递过来的属性:

Vue

如果不加上 ​​inheritAttrs=false​​ 属性,就会自动继承父组件传递过来的属性:

Vue

再看下 ​​$listeners​​ 的定义:

包含了父作用域中的 (不含 ​​.native​​ 修饰器的) ​​v-on​​ 事件监听器。它可以通过 ​​v-notallow="$listeners"​​ 传入内部组件 —— 在创建更高层次的组件时非常有用。

​$listeners​​ 也能把父组件中对子组件的事件监听全部拿到,这样我们就能用一个 ​​v-on​​ 把这些来自于父组件的事件监听传递到下一级组件。

继续改造 ComponentB 组件:

<template>
<div>
I am component B
<component-c v-bind="$attrs" v-on="$listeners"></component-c>
</div>
</template>
<script>
import ComponentC from @/components/ComponentC.vue

export default
name: ComponentB,
inheritAttrs: false,
components:
ComponentC


</script>
复制代码

这里利用 ​​$attrs​​ 和 ​​$listeners​​ 方法,可以将祖先组件 (ComponentA) 中的属性和事件透传给孙组件 (ComponentC),这样就可以实现隔代组件之间的通信。

provide/inject

​provide/inject​​ 是 Vue 2.2.0 版本后新增的方法。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

先看下简单的用法:

父级组件:

export default 
provide:
name: Lin


复制代码

子组件:

export default 
inject: [name],
mounted ()
console.log(this.name); // Lin


复制代码

上面的例子可以看到,父组件通过 ​​privide​​ 返回的对象里面的值,在子组件中通过 ​​inject​​ 注入之后可以直接访问到。

但是需要注意的是,​​provide​​ 和 ​​inject​​ 绑定并不是可响应的,按照官方的说法,这是刻意为之的

也就是说父组件 provide 里面的 name 属性值变化了,子组件中 this.name 获取到的值不变。

如果想让 provide 和 inject 变成可响应的,有以下两种方式:

  • provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 props,methods
  • 使用 Vue 2.6 提供的 Vue.observable 方法优化响应式 provide

看一下第一种场景:

祖先组件组件 (ComponentA):

export default 
name: ComponentA,
provide()
return
app: this

,
data()
return
appInfo:
title:


,
methods:
fetchAppInfo()
this.appInfo = title: Welcome to Vue world



复制代码

我们把整个 ComponentA.vue 的实例 ​​this​​ 对外提供,命名为 ​​app​​。接下来,任何组件只要通过 ​​inject​​ 注入 app 的话,都可以直接通过 ​​this.app.xxx​​ 来访问 ComponentA.vue 的 ​​data​​、​​computed​​、​​methods​​ 等内容。

子组件 (ComponentB):

<template>
<div>
title
<button @click="fetchInfo">获取App信息</button>
</div>
</template>
<script>
export default
name: ComponentB,
inject: [app],
computed:
title()
return this.app.appInfo.title

,
methods:
fetchInfo()
this.app.fetchAppInfo()



</script>
复制代码

这样,任何子组件,只要通过 ​​inject​​ 注入 ​​app​​ 后,就可以直接访问祖先组件中的数据了,同时也可以调用祖先组件提供的方法修改祖先组件的数据并反应到子组件上。

当点击子组件 (ComponentB) 的获取 App 信息按钮,会调用 ​​this.app.fetchAppInfo​​ 方法,也就是访问祖先组件 (ComponentA) 实例上的 fetchAppInfo 方法,fetchAppInfo 会修改 fetchAppInfo 的值。同时子组件 (ComponentB) 中会监听 this.app.appInfo 的变化,并将变化后的 title 值显示在组件上。

再看一下第二种场景,通过 ​​Vue.observable​​ 方法来实现 ​​provide​​ 和 ​​inject​​ 绑定并可响应。

基于上面的示例,改造祖先组件 (ComponentA):

import Vue from vue

const state = Vue.observable( title: );

export default
name: ComponentA,
provide()
return
state



复制代码

使用 ​​Vue.observable​​ 定义一个可响应的对象 state,并在 provide 中返回这个对象。

改造子组件 (ComponentB):

<template>
<div>
title
<button @click="fetchInfo">获取App信息</button>
</div>
</template>
<script>
export default
name: ComponentInject,
inject: [state],
computed:
title()
return this.state.title

,
methods:
fetchInfo()
this.state.title = Welcome to Vue world22



</script>
复制代码

与之前的例子不同的是,这里我们直接修改了 this.state.title 的值,因为 state 被定义成了一个可响应的数据,所以 state.title 的值被修改后,视图上的 title 也会立即响应并更新,从这里看,其实很像 ​​Vuex​​ 的处理方式。

以上两种方式对比可以发现,第二种借助于 ​​Vue.observable​​ 方法实现 ​​provide​​ 和 ​​inject​​ 的可响应更加简单高效,推荐大家使用这种方式。

基于 ​​$parent​​/​​$children​​ 实现的 ​​dispatch​​ 和 ​​broadcast​

先了解下 dispatch 和 broadcast 两个概念。

  • dispatch: 派发,指的是从一个组件内部向上传递一个事件,并在组件内部通过$on 进行监听
  • broadcast: 广播,指的是从一个组件内部向下传递一个事件,并在组件内部通过$on 进行监听

在实现 dispatch 和 broadcast 方法之前,先来看一下具体的使用方法。有 ComponentA.vue 和 ComponentB.vue 两个组件,其中 ComponentB 是 ComponentA 的子组件,中间可能跨多级,在 ComponentA 中向 ComponentB 通信:

组件 ComponentA:

<template>
<button @click="handleClick">派发事件</button>
</template>
<script>
import Emitter from ../mixins/emitter.js;
export default
name: ComponentA,
mixins: [Emitter],
methods:
handleClick ()
this.dispatch(ComponentB, on-message, Hello Vue.js)



</script>
复制代码

组件 ComponentB:

export default 
name: ComponentB,
created ()
this.$on(on-message, this.showMessage)
,
methods:
showMessage (text)
console.log(text)



复制代码

dispatch 的逻辑写在 ​​emitter.js​​ 中,使用的时候通过 ​​mixins​​ 混入到组件中,这样可以很好的将事件通信逻辑和组件进行解耦。

dispatch 的方法有三个传参,分别是:需要接受事件的组件的名字 (全局唯一,用来精确查找组件)、事件名和事件传递的参数。

dispatch 的实现思路非常简单,通过 ​​$parent​​ 获取当前父组件对象,如果组件的 name 和接受事件的 name 一致 (dispatch 方法的第一个参数),在父组件上调用 ​​$emit​​ 发射一个事件,这样就会触发目标组件上 ​​$on​​ 定义的回调函数,如果当前组件的 name 和接受事件的 name 不一致,就递归地向上调用此逻辑。

dispath:

export default 
methods:
dispatch(componentName, eventName, params)
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName))
parent = parent.$parent;
if (parent)
name = parent.$options.name


if (parent)
parent.$emit.apply(parent, [eventName].concat(params));




复制代码

broadcast 逻辑和 dispatch 的逻辑差不多,只是一个是通过​​ $parent​​ 向上查找,一个是通过 ​​$children​​ 向下查找,

export default 
methods:
broadcast(componentName, eventName, params)
this.$children.forEach(child
const name = child.$options.name
if (name === componentName)
child.$emit.apply(child, [eventName].concat(params))
else
broadcast.apply(child, [componentName, eventName].concat([params]))

)


完整附件​​点此下载​

以上是关于vue中组件间通信的6种方式的主要内容,如果未能解决你的问题,请参考以下文章

vue组件间通信六种方式(完整版)

vue组件间通信六种方式(完整版)

Vue.js 父子组件通信的十种方式

一文快速详解前端框架 Vue 最强大的功能

聊聊Vue.js组件间通信的几种姿势

vuex基础总结