Vue 父子跨级通讯 dispatch与broadcast

https://www.cnblogs.com/raind/p/10241720.html
https://blog.csdn.net/infotw/article/details/91374480

技术没有好与坏,只有合适不合适

问题

为什么被移除

  1. 没有解决兄弟组件的通信问题
  2. 事件流动的方式是基于组件树结构的,当业务越来越烦杂时会混乱到难以维护

为什么使用

  • 解决在父子层嵌套组件
  • 通过$dispatch和broadcast定向的向某个父或者子组件远程调用事件,这样就避免了通过传props或者使用refs调用组件实例方法的操作。

代码

// Vue
/**
 * Recursively broadcast an event to all children instances.
 * 递归地向所有子实例广播事件。
 * @param {String|Object} event
 * @param {...*} additional arguments
 */
// $dispatch 方法是定义在 Vue 的 prototype 上的
// 接受一个事件
Vue.prototype.$broadcast = function (event) {
 // 获取传入事件的类型,判断是否为字符串
 var isSource = typeof event === 'string'
  
 // 校正 event 的值,当接受 event 的类型为字符串时就直接使用,如果不是字符串就使用 event 上的 name 属性 
 event = isSource ? event : event.name
  
 // if no child has registered for this event,
 // then there's no need to broadcast.
 // 如果当前组件的子组件没有注册该事件,就直接返回,并不用 broadcast
 if (!this._eventsCount[event]) return
  
 // 获取当前组件的子组件
 var children = this.$children
  
 // 将函数接受的参数转换成数组
 var args = toArray(arguments)
  
 // 如果传入事件为字符串
 if (isSource) {
  // use object event to indicate non-source emit
  // on children
  // 根据传入的事件名称的参数组装成 object
  args[0] = { name: event, source: this }
 }
  
 // 循环子组件
 for (var i = 0, l = children.length; i < l; i++) {
  var child = children[i]
   
  // 在每个子组件中调用 $emit 触发事件
  var shouldPropagate = child.$emit.apply(child, args)
   
  // 判断调用 $emit 返回的值是否为 true
  if (shouldPropagate) {
   // 如果调用 $emit 返回的值为 true,就递归孙子组件继续广播
   child.$broadcast.apply(child, args)
  }
 }
  
 // 最后返回当前组件的实例
 return this
}

// element-ui
/**
 * 派发和广播
 * 核心思想是通过递归或遍历来查找要broadcast或dispatch的组件名字,然后在组件自身上emit与on
 * @param {*} componentName 需要触发事件的组件名称
 * @param {*} eventName 将要触发的事件名称
 * @param {*} params 回调函数传递的参数
 */
function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    var name = child.$options.componentName;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

// 使用
// parent.vue

<template>
  <div>
    <h1>我是父组件</h1>
    <button @click="handleClick">触发事件</button> <child />
  </div>
</template>
<script>
import Emitter from "@/mixins/emitter.js";
import Child from "./child";
export default {
  name: "componentA",
  mixins: [Emitter],
  created() {
    this.$on("child-to-p", this.handleChild);
  },
  methods: {
    handleClick() {
      this.broadcast("componentB", "on-message", "Hello Vue.js");
    },
    handleChild(val) {
      alert(val);
    }
  },
  components: {
    Child
  }
};
</script>


// child.vue

<template>
  <div>我是子组件</div>
</template>
<script>
import Emitter from "@/mixins/emitter.js";
export default {
  name: "componentB",
  mixins: [Emitter],
  created() {
    this.$on("on-message", this.showMessage);
    this.dispatch("componentA", "child-to-p", "hello parent");
  },
  methods: {
    showMessage(text) {
      window.alert(text);
    }
  }
};
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值