vue组件的通信

对于vue来说,组件是必不可少的,很多平台都封装了自己的组件库,如elementUi、viewUi 等等 。同时组件之间的通信也是非常重要的,下面带领大家总结一下常用的几种通信方式

组件通信分类
  • 父子组件

  • 非父子组件(兄弟组件、隔代关系组件等)

本文会介绍组件通信的八种方式,并介绍在什么样的场景下需要什么样的通信方式,希望帮助你们更多的了解组件间的通信
  • props与$emit

  • $children与$parent

  • provide与reject

  • ref

  • eventBus(中央事件线)

  • Vuex

  • localStorage与sessionStorage

  • $attrs与$listeners

1.props与$emit
父组件通过props想子组件传递数据,而通过$emit子组件可以向父组件通信

父组件向子组件传递数据

下面通过一个简单的例子说明一下这个通信方式:在子组件中如何获取父组件中的数据
// 父组件<template>    <div>    <demo :list="toDoList">demo>  div>template><script>import demo from './demo'export default{  data(){    return {      toDoList:['今天上午去逛街','今天中午休息','下午三点有个约会','下午六点要去买菜']    }  },  components:{    demo  }}script>// 子组件<template>    <div>        <span v-for="(item, index) in list" :key="index">{{item}}span>    div>template><script>export default {    data(){        return {                   }    },    // props的普通接收数据方式    // props:['list'],        // props自带类型校验数据类型的接收数据方式 如果父组件传递的数据和此处接收的定义类型不一致 就会发出警告    props:{        list:{            type:Array,            default:[]        }    }}script>

dom渲染 32d295805b81fd8d25769b89602ff4b5.png


子组件向父组件传递数据

$emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数。通过一个例子,说明子组件如何向父组件传递数据

// 父组件<template>  <div>    <demo :list="toDoList" @sendIndex="sendIndex">demo>  div>template><script>import demo from './demo'export default{  data(){    return {      toDoList:['今天上午去逛街','今天中午休息','下午三点有个约会','下午六点要去买菜']    }  },  methods:{    sendIndex(data){      console.log (`监听自定义事件,接收来自子组件的消息:index是${data}`)    }  },  components:{    demo  }}script>//子组件<template>  <div>    <p v-for="(item, index) in list" :key="index" @click="emitIndex(index)">{{item}}-{{index}}p>  div>template><script>export default {  data() {    return {};  },  //props的普通接收数据方式  // props:['list'],  // props自带类型校验数据类型的接收数据方式 如果父组件传递的数据和此处接收的定义类型不一致 就会发出警告  props: {    list: {      type: Array,      default: []    }  },  methods: {    emitIndex(message) {      console.log(`此处通过自定义事件sendIndex, 传递信息给父组件`);      this.$emit("sendIndex", message);    }  }};script>

控制台显示 a4728108a3d43dc72061a9b29798111f.png

2.$children / $parent

通过$parent$children就可以访问组件的实例,拿到实例代表什么?代表可以访问此组件的所有方法和data。接下来就是怎么实现拿到指定组件的实例。使用方法

// 父组件<template>  <div class="hello_world">    <div>{{msg}}div>    <com-a>com-a>    <button @click="changeA">点击改变子组件值button>  div>template><script>import ComA from './demo'export default {  components: {    ComA  },  data() {    return {      msg: '父组件上的msg值'    }  },  methods: {    changeA() {      //  this.$children[0] 获取到第一个子组件的实例      this.$children[0].message = '父组件修改子组件的数据 新的值'    }  }}script>//子组件<template>  <div class="com_a">    <span>{{message}}span>    <p>获取父组件的值为: {{parentVal}}p>  div>template><script>export default {  data() {    return {      message: '这是子组件的初始数据 旧的值'    }  },  computed: {    parentVal() {      //this.$parent获取到父组件实例      return this.$parent.msg;    }  }}script>
注意事项:要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent和$children的值不一样,$children 的值是数组,而$parent是个对象
上面两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍; 二者皆不能用于非父子组件之间的通信。
3.provide/ inject
概念:

provide/ inject 是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。

注意: 这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据

接下来就用一个例子来验证上面的描述: 假设有三个组件: comA.vue、comB.vue、comc.vue 其中 comC是comB的子组件,comB是comA的子组件
// comA组件<template>  <div class="hello_world">    <com-b>com-b>  div>template><script>import ComB from './comB'export default {  components: {    ComB  },  provide:{    for:'我是comA组件提供的数据'  },  data() {    return {          }  },  methods: {      }}script>// comB组件<template>  <div>    <div class="inject-comB">{{demo}}div>    <comC>comC>   div>template><script>import comC from './comC'export default {  data() {    return {      //展示 comA提供的数据      demo: this.for    }  },  //inject  注入comA提供的数据  inject: ['for'],  components:{    comC  }}script>//comC组件<template>  <div class="inject-comC">{{demo}}div>template><script>export default {  //注入comA组件提供的数据  inject: ["for"],  data() {    return {      //显示comA提供的数据      demo: this.for    };  }};script>

dom渲染 4861e5bca258944c2c2c37a556f2d06b.png

4.ref / refs

如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据, 我们看一个ref 来访问组件的例子:

//父组件<template>  <div class="hello_world">        <com-b ref="comB">com-b>   div>template><script>import ComB from './comB'export default {  components: {    ComB  },  data() {    return {          }  },  methods: {      },  mounted(){    // 通过ref获取comB的实例    const comB = this.$refs.comB;    console.log(comB)    comB.changeMsg()  }}script>// 子组件<template>  <div>    <p>{{msg}}p>    <el-button @click="changeMsg">修改信息el-button>   div>template><script>export default {  data() {    return {      msg: '我是信息'    }  },  methods:{    changeMsg(){      this.msg = '我是修改的信息'    }  },  components:{      }}script>

通过控制台 我们可以看下comB实例是什么 caa78d593f80cb9171be617bb8fbbdc1.png

5.eventBus

公共事件总线eventBus的实质就是创建一个vue实例,通过一个空的vue实例作为桥梁实现vue组件间的通信。它是实现非父子组件通信的一种解决方案 那么如何使用eventBus呢?

第一步:项目中创建一个js(通过我会把它命名为bus.js),引入vue,创建一个vue实例,并导出这个实例 如下

//  bus.js    import Vue from 'vue'    export default new Vue()

第二步: 在需要通信的两个组件中分别引入这个bus.js

import Bus from '这里引入bus.js的路径'// Bus可换成其他名字// 组件A<template>  <div>    <p>{{msg}}p>    <el-button @click="changeMsg">修改信息el-button>   div>template><script>import Bus from '../bus'export default {  data() {    return {      msg: '我是子组件信息'    }  },  methods:{    changeMsg(){      console.log(this.msg)    }  },  components:{      }}script>// 组件B<template>  <div class="hello_world">        <com-b>com-b>   div>template><script>import ComB from './comB'import Bus from '../bus'export default {  components: {    ComB  },  data() {    return {          }  },  methods: {      },  mounted(){    }}script>

第三步:传递数据的组件里通过vue实例方法$emit发送事件名称和需要传递的数据。(发送数据组件)

// 组件B 传递数据<template>  <div>    <p>{{msg}}p>    <el-button @click="changeMsg">修改信息el-button>   div>template><script>import Bus from '../bus'export default {  data() {    return {      msg: '我是子组件信息'    }  },  methods:{    changeMsg(){      Bus.$emit('sendMsg',this.msg)    }  },  components:{      }}script>

第四步:接受传递数据的组件内通过vue实例方法$on监听事件和接受到数据。(接收数据的组件)这里通常挂载监听在vue生命周期created和mounted当中的一个

//组件A接收数据<template>  <div class="hello_world">        <com-b>com-b>   div>template><script>import ComB from './comB'import Bus from '../bus'export default {  components: {    ComB  },  data() {    return {          }  },  methods: {      },  mounted(){    Bus.$on('sendMsg',data=>{        console.log(data)  //我是子组件信息    })  }}script>

注意:如果使用不善,EventBus会是一种灾难,到底是什么样的“灾难”了?大家都知道vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。还有就是如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理EventBus在项目中的关系。通常会用到,在vue页面销毁时,同时移除EventBus事件监听。 你也可以使用 Bus.$off('sendMsg') 来移除应用内所有对此某个事件的监听。或者直接调用 Bus.$off() 来移除所有事件频道,不需要添加任何参数 。

第五步:在vue生命周期beforeDestroy或者destroyed中用vue实例的$off方法清除eventBus

destroyed(){    Bus.$off('sendMsg')}
6.vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. Vuex 解决了多个视图依赖于同一状态和来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上

关于vuex的相关知识 会在后面详细介绍 此处就先不介绍了
7.localStorage / sessionStorage
这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。通过

window.localStorage.getItem(key)获取数据 通过 window.localStorage.setItem(key,value)存储数据

注意用JSON.parse() / JSON.stringify() 做数据格式转换 localStorage / sessionStorage可以结合vuex, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题.

8.$attrs与 $listeners

2.4.0 新增

  • 类型:{ [key: string]: string }

  • 只读

  • 详细:

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

官方说法似乎有点模糊 其实可以这么去理解
因为在项目中深层次组件交互的话可能就需要 Vuex 助力了,但是如果只是一个简单的深层次数据传递,

或者进行某种交互时需要向上通知顶层或父层组件数据改变时,大材小用Vuex可能未免有点多余!
$attr 与 interitAttrs 之间的关系
interitAttrs:

2.4.0 新增

  • 类型:boolean

  • 默认值:true

  • 详细:

默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 到 false,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

warn:这个选项不影响 class 和 style 绑定。

下面通过简单demo解释下

父组件

<template>  <div class="hello">     <demo :count="count" :num="num">demo>  div>template><script>  import Demo from './demo.vue'  export default {    name: 'hello',    components: {      Demo    },    data () {      return {        count: 'count message',        num: 'num message'      }    },  }script>

子组件

<template>   <div>      {{count}}   div>template><script>export default {   name: 'demo',   props: ['count']}script>

父组件在子组件中进行传递 count 和num 两个数据,在子组件中,应该有相对应的 props 定义的接收点,如果在 props 中定义了,你会发现无论是 count 和 num 都成了子组件的接收来的数据了,可以用来进行数据展示和行为操作。虽然在父组件中在子组件模版上通过 props 定义了两个数据,但是子组件中的 props 只接收了一个,只接收了 count,并没有接收 num,没有进行接收的此时就会成为子组件根无素的属性节点。

interitAttrs = false 发生了什么 ?

子组件

<template>   <div>      {{count}}   div>template><script>export default {   name: 'demo',   props: ['count'],   interitAttrs:false}script>
对子组件进行一个改动,我们加上 inheritAttrs: false,从字面上的翻译的意思,取消继承的属性,然而 props 里仍然没有接收 num,发现就算 props 里没有接收 num,在子组件的根元素上并没有绑定任何属性。

$attrs

在前面的例子中,子组件props中并没有接受num,设置选项 inheritAttrs:false,同样也不会作为根元素的属性节点,整个没有接收的数据都被 $attr 实例属性给接收,里面包含着所有父组件传入而子组件并没有在 Props里显示接收的数据。

为了验证事实,可以在子组件中加上

created () {       console.log(this.$attrs)    }

打印出来则是一个对象 {num: "num message"}

warn

想要通 $attr 接收,但必须要保证设置选项 inheritAttrs: false,不然会默认变成根元素的属性节点。开头说了,最有用的情况则是在深层次组件运用的时候,创建第三层孙子组件,作为第二层父组件的子组件,在子组件引入的孙子组件,在模版上把整个 $attr 当数作数据传递下去,中间则并不用通过任何方法去手动转换数据。子组件

<template>   <div>      <next-demo v-bind="$attrs">next-demo>   div>template><script>import nextDemo from './nextdemo.vue'export default {   name: 'demo',   components: {      nextDemo    },   created () {       console.log(this.$attrs)    }}script>

孙子组件

<template>  <div>      {{count}}{{num}}  div>template><script>  export default {     props : [ 'count' , 'num']  }script>

孙子组件在 props 接收子组件中通过 $attrs 包裹传来的数据,同样是通过父组件传来的数据,只是在子组件用了$attrs进行了统一接收,再往下传递,最后通过孙子组件进行接收。以此类推孙子组件仍然不想接收,再传入下级组件,我们仍然需要对孙子组件实力选项进行设置选项 inheritAttrs: false,否则仍然会成为孙子组件根元素的属性节点。从而利用 $attrs 来接收 props 为接收的数据再次向下传递是一件很方便的事件,深层次接收数据我们理解了,那从深层次向层请求改变数据如何实现。意思就是让顶层数据和最底层数据进行一个双向绑定。


$listeners

2.4.0 新增

类型:{ [key: string]: Function | Array }

只读

详细:

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

$listeners 和 $attrs 两者表面层都是一个意思,$attrs 是向下传递数据,$listeners 是向下传递方法,通过手动去调用 $listeners 对象里的方法,原理就是 $emit 监听事件,$listeners 也可以看成一个包裹监听事件的一个对象。父组件

 <template>      <div class="hello">         {{firstMsg}}         <demo v-on:changeData="changeData" v-on:another = 'another'>demo>      div>    template>        <script>      import Demo from './demo.vue'      export default {        name: 'hello',        components: {          Demo        },        data () {          return {            firstMsg: '父组件',          }        },        methods: {          changeData (params) {             this.firstMsg = params          },          another () {            alert(2)          }        }      }script>

子组件

<template>       <div>          <p @click="$emit('another')">子组件p>            <next-demo  v-on='$listeners'>next-demo>       div>           template>    <script>    import NextDemo from './nextdemo.vue'    export default {       name: 'demo',       components: {           NextDemo       },       created () {         console.log(this.$listeners)       },    }script>

孙子组件

<template>      <div class="hello">          <p @click='$listeners.changeData("change")'>孙子组件p>      div>    template>        <script>    export default {      name: 'demo',      created () {          console.log(this.$listeners)      },    }script>

依然能拿到从子组中传递过来的$listeners所有的监听事件,此时并不是通过$emit去触发,而是像调用函数一样,$emit只是针对于父子组件的双向通信,$listeners包了一个对象,分别是 changeData 和 another,通过$listeners.changeData('change')等于直接触发了事件,执行监听后的回调函数,就是通过函数的传递,调用了父组件的函数。通过 $attrs 和 $listeners 可以很愉快地解决深层次组件的通信问题,更加合理的组织你的代码。

开心一下
医院重病看护中心,有一重病患者清醒过来,看到护士就问:你说,我的寿命还有多长时间?护士:这个?你得去问阎王!患者:上帝能要我吗?护士:不知道!患者:你们不是天使吗?护士:我们只管接送,不管审批!!!
下一节 组件开发
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐