# 组件间通信

  • props$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 $emit 触发事件来做到的
  • $attrs$listeners A->B->C。Vue 2.4 开始提供了$attrs和$listeners来解决这个问题
  • $parent$children 获取父子组件实例
  • $refs 获取实例
  • 父组件中通过 provider 来提供变量,然后在子组件中通过 inject 来注入变量
  • eventBus 创建一个EventBus类负责事件派发、监听和回调管理
  • Vuex 创建唯一的全局数据管理者store,通过它管理数据并通知组件状态变更

# props/$emit

//App.vue父组件
<template>
  <div id="app">
    <!-- 传递属性 --> 
    <users v-bind:users="users"></users> 
  </div>
</template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "users":Users
  }
}
//users子组件
<template>
  <div class="hello">
    <ul>
      <!-- 遍历属性 --> 
      <li v-for="user in users">{{user}}</li>
    </ul>
  </div>
</template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    // 定义属性名及参数验证  
    users:{
      type: Array,
      required: true
    }
  }
}
</script>

# $attrs/$listeners

多级组件嵌套需要传递数据时,如果仅仅是传递数据,而不做中间处理,为此Vue2.4 版本提供了另一种方法---- $attrs/$listeners

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

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

简单来说:$attrs$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners 里存放的是父组件中绑定的非原生事件。

// child:并未在props中声明foo
<p>{{$attrs.foo}}</p>
// parent
<HelloWorld foo="foo"/>

# $parent / $children

  • $parent:获取父组件实例,如果当前实例有的话。
  • $children:当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。

可以直接调用组件的方法或访问数据。

需要注意 $children 并不保证顺序,因为组件有可能是异步的。也不是响应式的。

子组件向上派发事件 触发指定父组件的自定义事件

/**
 * 
 * @param {*} eventName 事件名
 * @param {*} componentName 组件名
 * @param {*} value 子组件向父组件传递的数据
 * 子组件向上派发事件 触发指定父组件的自定义事件 
 */
Vue.prototype.$dispatch = function (eventName,componentName, value){
    let parentCom = this.$parent;
    while (parentCom){
        // 向上派发事件 触发指定父组件的自定义事件 
        if (parentCom.$options.name === componentName){
            parentCom.emit(eventName, value); // 触发自定义事件  没有绑定不会触发
            break;
        }
        parentCom = this.$parent;
    }
}

父组件向下派发事件 触发指定子组件的自定义事件

/**
 *
 * @param {*} eventName 事件名
 * @param {*} componentName 组件名
 * @param {*} value 父组件向子组件传递的数据
 * 父组件向下派发事件 触发指定子组件的自定义事件
 */
Vue.prototype.$broadcast = function (eventName, componentName, value) {
    let children = this.$children;
    function broadcast(children) {
        for (let i = 0; i < children.length;i++){
            let child = children[i];
            if (child.$options.name === componentName){
                child.emit(eventName, value); // 触发自定义事件  没有绑定不会触发
                return;
            }else{
                child.$children && broadcast(child.$children); // 递归遍历子节点
            }
        }
    }
    broadcast(children)
}

# ref

被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:

<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>

<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
// parent
<HelloWorld ref="hw"/>

// parent组件直接修改 HelloWorld 子组件数据
mounted() {
  this.$refs.hw.xx = 'xxx'
}

v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。

关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。

# provide/inject

Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。

provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

会造成单向数据流混乱,不好追溯数据来源,通常用于工具库中。

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

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

# eventBus

事件总线:创建一个EventBus类负责事件派发、监听和回调管理

利用new 一个新的vue实例,可以轻松实现组件之间数据通信。

import Vue from 'vue'

const app= new Vue({
    el:'#app',
	router,
	store
});

Vue.prototype.$eventBus = new Vue();
// Boy组件 发射dinner事件
<template>
    <div>男孩
        <button @click="sayToGirl()">对女孩说话</button>
    </div>
</template>
<script>
export default {
    methods: {
        sayToGirl(){
           this.$bus.$emit('dinner','你饿吗');
        }
    }
}
</script>
// Girl组件 监听dinner事件
<template>
    <div>
    女孩 <span>男孩对我说: {{message}}</span>
    </div>
</template>


<script>
export default {
    data(){
        return {message:''}
    },
    mounted() {
        this.$bus.$on('dinner',(data)=>{
            this.message = data;
        })
    }
}
</script>

# 基于发布订阅实现 EventBus

实践中可以用Vue代替EventBus,因为它已经实现了相应功能

class EventBus {
  constructor() {
    this.callbacks = {};
  }
  $on(name, fn) {
    this.callbacks[name] = this.callbacks[name] || [];
    this.callbacks[name].push(fn);
  }
  $emit(name, args) {
    if (this.callbacks[name]) {
      // 存在 遍历所有callback
      this.callbacks[name].forEach(cb => cb(args));
    }
  }
}
更新时间: 6/4/2020, 4:37:04 PM