Vue组件通信

简介

组件是Vue的核心,而组件间的状态管理和数据传递是开发绕不开的问题。在Vue中,组件和组件之间是相互独立的,所以需要一定的方法才能进行Vue的组件间的通信。

基础技能 props和$emit

如果你连这个都不会用的话就一定要好好看一下这篇文章和官方文档

结合场景分析:现在我们有一个获取客户列表的功能,并且要展示客户的信息。
那么我们先来开发一个customer-item的客户详情组件

<template>
<!-- 客户信息组件 -->
  <div>
    <input type="text" v-model="selfName" /> <button @click="rename">Rename</button>{{name}}
  </div>
</template>

<script>
export default {
  props: {
    name: String,
    index: Number
  },
  data() {
    return {
      selfName: ''
    }
  },
  mounted() {
    this.selfName = this.name
  },
  methods: {
    rename() {
      this.$emit('rename', this.index, this.selfName)
    }
  },
}
</script>


customer可以通过props来接收父组件向下通过属性传播的值,这就是父向子的一个通信。而子组件如果想要更新父组件的状态,是不能直接去向上修改状态的。这时候就需要通过事件来完成这个操作,也就是通过$emit来触发父组件定义的一个事件(在上面代码中就是触发父组件的rename事件),同时可以将要修改的数据当作参数传递,即可在父组件中完成状态更新。

进阶技能1 $attrs$listeners

首先,这两个是Vue 2.4.0 版本新出的属性,所以如果没有接触过的话就需要好好补充一下自己的技能包了。

这两个属性可以说是 props和$emit 的一个补充增强。按照我们之前的写法,父组件像自组件传递参数其实是按照属性的形式向下传递。那么其实对于自组件而言,我们可以无需关心父组件具体向我们传递了哪些东西,我们能够拿到这些属性值即可。

第二个cutomer组件:

<!--父组件-->
<Customer v-for="(item,index) in customers" :key="item.name" :index="index" @add="addOrder" :name="item.name" :order="item.order"/> 

<!--子组件-->
<template>
  <div>
    <div>姓名: {{$attrs.name}}</div>
    <div>订单数: {{$attrs.order}}<button @click="add">增加</button></div> 
  </div>
</template>
<script>
export default {
  data() {return {}},
  methods: {
    add() {
      this.$listeners.add(this.$attrs.index)
    }
  },
}
</script>

这样,自组件就通用了很多,不需要在props去声明那么多的属性,而触发方法的emit也可以通过$listeners来引用父组件中绑定在自组件的事件,即可完成父子组件的通信工作。而这两个属性 最便捷的作用还是可以通过v-bind="$attrs"来完成$attrs的一个属性传递和v-on="$listeners"的一个事件传递来实现子组件与祖先组件的一个通信。如上述的代码,我可以改造为:

<!--Customer组件-->
<template>
  <div>
    <div>姓名: {{$attrs.name}}</div>
    <Order v-bind="$attrs" v-on="$listeners"/>
  </div>
</template>
<script>
import Order from './Order.vue'
export default {
  data() {return {}},
  components: {
    Order
  }
}
</script>
<!--Order组件-->
<template>
  <div>
    订单数: {{$attrs.order}}<button @click="add">增加</button>
  </div>
</template>
<script>
export default {
  methods: {
    add() {
      this.$listeners.add(this.$attrs.index)
    }
  },
}
</script>

那么,在设计一些较为复杂的组件时,使用$attrs$listeners要比props+$emit的组合要好用太多了

Tips: 在使用$attrs来进行向下的属性传递时,会默认将这些属性附加到自组件上,如图

inheritAttrs.png

这种行为是默认的,如果不希望默认这种行为,则可以通过设置inheritAttrs为false来阻止这个默认行为:


inheritAttrs2.png

进阶技能2 eventBus($emit, $on

上面的两种方法都是均常用于长辈组件和晚辈组件的数据通信,而对于兄弟组件而言,要想通过上述的方法实现效果,就需要父组件做一个中转站(父组件用来管理状态,A组件修改状态,通过事件通知父组件,父组件再修改状态来达到修改B组件状态的效果),这无疑是一个没必要的开销,而且如果兄弟组件多的时候,父组件中的状态会非常的冗余。

eventBus可以作为一个事件的转发中心,对于组件而言,均可以注册eventBus,而某个组件触发事件时,注册了这个事件的组件均会触发事件并且执行对应的方法。这个过程可以通过下面这个图理解:

eventBus.png

那么其实在eventBus中,我们不再关心组件和组件间具体是父子还是兄弟还是祖先的关系,而是将重点放在通过事件来进行组件间的交互。当然,eventBus还是最常用语兄弟组件或者跨级组件这种场景。

// 先创建一个eventBus,Vue的实例对象就是一个天然的eventBus

import Vue from 'vue'
const eventVue = new Vue()
export default eventVue

// 组件A
<script>
import eventBus from './eventBus'
export default {
  data() {
    return {
      msg: ''
    }
  },
  methods: {
    sendMsg() {
    // 触发事件
      eventBus.$emit('sendMsg', this.msg)
    }
  },
}
</script>

// 组件B
<script>
import eventBus from './eventBus'
export default {
  data() {
    return {
      msg: ''
    }
  },
  mounted() {
    // 注册事件
    eventBus.$on('sendMsg', val => {
      this.msg = val
    })
  }
}
</script>

进阶技能3 provide&inject

掌握上面的三个技能,在大多数的组件交互中已经够用了。不过在组件给其他组件传递状态时,不管是使用props还是$attrs都是有一些繁琐。在Vue 2.2 的时候新增的provide,inject属性就很适合这种数据传递的场景。

仅从字面意思上来理解这个东西,组件A提供几个状态,组件B注入这些状态。不过仅在使用provide&inject是没有办法实现数据的响应式的。最基本的用法如下所示:

语法:
provide:Object | () => Object
inject:Array<string> | { [key: string]: string | Symbol | Object }


// 父组件
<script>
import Customer from '../components/provide/Customer'
export default {
  data() {
    return {
      info: {
        name: 'wyh',
        order: 18
      }
    }
  },
  components: {
    Customer
  },
  provide() {
    return {
      name: this.info.name,
      order: this.info.order
    }
  }
}
</script>

// 子组件
<template>
  <div>
    姓名: {{name}}
    <Order />
  </div>
</template>
export default {
  inject: ['name']
}

// 孙组件
export default {
  inject: ['order']
}

效果:


provide1.png

inject注入状态的写法一种是上面的字符串数组形式的。
另一种是对象形式。如果你对当前组件中注入的这个状态有更多修饰时使用,比如重命名等,代码如下:

// 重命名
inject: {
  selfOrder: 'order'
}
// 设置默认值
inject: {
 order: {
    from: 'order',
    default: 17
  }
}

provide和inject不是响应式的,不过如果传入的是一个可以监听的对象,那么其属性就也可以实现成响应式。这里要使用Vue提供的静态方法Vue.observable(2.6版本后可用)代码如下:

// 父组件
provide() {
    // 生成响应式对象
    this.data = Vue.observable(this.info)
    return {
      name: this.info.name,
      order: this.info.order,
      data: this.data
    }
  }

// 子组件
<template>
  <div>
    姓名: {{name}} <br />
    响应式的姓名: {{data.name}}
    <Order />
  </div>
</template>

<script>
import Order from './Order'
export default {
  inject: ['name', 'data'],
  components: {
    Order
  }
}
</script>

效果:


observable.gif

最终技能 Vuex

Vuex的内容就太多了。不如放几个思考题。

  1. Vuex如何进行状态管理,它的map工具是否会使用
  2. Vuex是如何完成状态更新的
  3. mutation和action的区别,为何mutation一定要是同步的
  4. Vuex模块化方案
  5. 能否对比一下redux

还有个黑科技 $ref $parent $children

由于Vue可以通过上面的三个属性来获取父组件,子组件的实例,也就可以直接去进行一些组件间的交互了。比如我修改一下props demo中的代码:

// 修改名称代码黑科技
rename() {
      // this.$emit('rename', this.index, this.selfName)
      this.$parent.$set(this.$parent.$data.names, this.index, this.selfName)
    }

依然是可以进行组件间的数据交互的。不过黑科技就是黑科技,还是不推荐使用,除非上面的几种形式均不满足要求的时候。

麻烦点个start咯 github地址 这个代码后续会完善Vuex的一些学习笔记

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 对于vue来说,组件之间的消息传递是非常重要的,下面是我对组件之间消息传递的各种方式的总结,总共有8种方式。 1....
    edc余悸阅读 371评论 0 3
  • 组件作为Vue中的核心概念,是值得我们深入研究的课题之一,通过研究它,我们可以理解更高深的思想,可以提升自己的开发...
    北辰_狼月阅读 781评论 2 7
  • 父子组件通信 1、父子组件通过prop传递数据 父组件可以将一条数据传递给子组件,这条数据可以是动态的,父组件的数...
    视觉派Pie阅读 1,287评论 0 18
  • 前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引...
    用技术改变世界阅读 2,184评论 1 3
  • 摘要: 总有一款合适的通信方式。 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有。 前言 组件是 v...
    Fundebug阅读 15,596评论 3 57