# 组件之间的通信

# 最基本的props/$emit父子组件之间

props/$emit 是父子组件之间最基本的通信了,父组件通过props的方式向子组件传递数据,而子组件可以通过$emit向父组件通信。

通过props/$emit这种基本方式演变的父子组件间的数据双向绑定通信还有 v-model / .sync

v-model指令

v-model本来是应用在表单上进行双向绑定的,他的本质是会默认为组件绑定名为 value 的 prop 和名为 input 的事件。后来通过 model 选项自定义 props和 event 名代替 props/$emit 在组件上实现数据的双向绑定。

参考链接:在组件上使用v-model

但是v-model指令在组件上只能绑定一个数据,只能使用一次,没有办法绑定多个,不过vue3中可以通过 v-model:event1=xxx 跟参数来添加多个v-model

.sync 修饰符

v-model 这种形式只能实现一个数据在组件上进行双向绑定,如果要实现多个数据进行双向绑定,可以是 .sync 修饰符 来简化操作。

参考链接:在组件上使用v-model

其实 .syncv-model 本质上都是相同的,都是通过 props/$emit 进行父子组件之间的通信,都不是真正的双向绑定,而是语法糖。只不过.sync更灵活一点,可以给绑定多个数据。

# $refs/$children$parent父子组件之间

父子组件通过$children$parent来相互各自的实例,调用其中的方法或属性进行相互操作,$refs则是父组件通过在子组件上绑定 ref 来获取和操作子组件的实例或DOM元素。

通过访问父组件实例 $parent

this.$parent  // 获取父组件实例
this.$parent.name  // 获取父组件数据
this.$parent.getValue() // 调用父组件方法

访问子组件实例 ref/ $children

this.$refs.son   // 获取子组件
this.$refs.son.age // 获取子组件数据
this.$refs.son.getAttr() // 调用子组件方法

this.$children     // 获取子组件数组
this.$children[0].age  // 获取子组件数据
this.$children[0].getAttr() // 调用子组件方法

# 使用中央事件总线,兄弟组件之间。

使用event Bus中央事件总线来通信,主要是通过创建一个Vue实例来当做桥梁,然后组件中引入这个实例,通过这个实例 $emit 提交事件,然后在另一个组件中通过 $on 来接收事件。

第一步:创建中间桥梁

// event-bus.js

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

第二步:发送事件

// A 组件
import EventBus from '@/event-bus'

methods: {
  // event bus emit
  emitEvent() {
    EventBus.$emit('EMIT_EVENT', data)
  }
}

第三步:接收事件

// B 组件
import EventBus from '@/event-bus'

created() {
  // event bus on
  EventBus.$on('EMIT_EVENT', (data) => {
    console.log(data)
  })
}

中央事件总线适合一些好维护的小项目,但是大项目就不合适了,状态多了就难维护了,最好使用vuex状态管理器。

# 使用 $attrs$listeners 跨组件

父子传值 props 和子传父 $emit 的话跨一级传递还好,但是跨多个级别传递的话就会非常麻烦,而$attrs$listeners就是他们的加强版,不需要每个都要传,只需要在每个组件上绑定这两个属性,那么绑定的组件就可以获取最顶级父组件传递的 props 值和子组件发送的 $emit 事件。

$attrs 来获取父组件传递的props值 this.$attrs.age

中间的组件都要 添加 v-bind='$attrs' v-on='$listeners' 这两个属性才能监听到,并且中间的组件也能监听到传递的 props 和发送的事件。

<div id="app">
  <!-- 最顶级父组件中接收孙组件传递的事件 -->
  <father-com :age="age" @send-msg="getMsg"></father-com>
</div>
var vm = new Vue({
  el: '#app',
  data: {
    age: 18
  },
  methods: {
    // 父组件获取到孙组件发送的事件
    getMsg: function(name) {
      console.log(name) // xxxxx
    }
  }
})
Vue.component('father', {
  inheritAttrs: true,
  template: `
    <div>
      <p>父组件:{{$attrs.age}}</p>
      <son v-bind='$attrs' v-on='$listeners'></son>
    </div>
  `
})
Vue.component('son', {
  inheritAttrs: true,
  methods: {
    getValue: function(name) {
      console.log(name) // xxxxx
    }
  },
  template: `
    <div>
      <p>子组件:{{$attrs.age}}</p>
      <grandson v-bind='$attrs' v-on='$listeners' @send-msg="getValue"></grandson>
    </div>
  `
})
// 孙组件进行发送事件
Vue.component('grandson', {
  inheritAttrs: true,
  methods: {
    sendMsg() {
      this.$emit('send-msg', 'xxxxx')
    },
  },
  template: `
    <div>
      <p>孙组件:{{$attrs.age}}</p>
      <button @click="sendMsg">发送事件</button>
    </div>
  `
})

# provide/inject 依赖注入跨组件

provide/inject 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。可以把依赖注入看作一部分“大范围有效的 prop”。

  • provide 选项应该是一个对象或返回一个对象的函数。
  • inject 选项应该是一个字符串数组,或一个对象

TIP

提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

提供属性:

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

提供方法:

// 父级组件提供 'getMap' 方法
var Provider = {
  provide: function () {
    return {
      getName: this.getName,
      msg: this.msg
    }
  }
  methods: {
    getName: function() {
      console.log('chang')
    },
  },
}
// 子组件注入 'getName'
var Child = {
  inject: ['getName', 'msg'],
  created () {
    console.log(this.msg) //
    console.log(this.getName()) // => 'chang'
  }
}

# 使用vuex和localStorage 跨组件

vuex状态管理器和localStorage本地存储都适合vue项目全局来通信,vuex比较庞大,比较适合大项目,vuex有个不好就是页面刷新随之里面的状态也被刷新了,我们可以结合localStorage来把状态存储在本地,刷新时可以先获取本地有没有,然后再保存在vuex中。