组件通信BUS模式及原理

864 阅读1分钟

vue组件通信方式是个老生常谈的话题了,最全面的莫过于 vuex,最简单的就是父子组件 props 传值,今天我们重点来说说经常提到的 bus 模式。

bus使用

  1. 使用Vue创建新实例
Vue.prototype.$bus = new Vue();

$bus 实际就是个 Vue 实例对象

  1. 定义事件监听函数
// A.vue
created() {
  this.$bus.$on('setName', (name) => {
    console.log('receive:' + name)
  })
}
  1. 触发事件
// B.vue
mounted() {
  this.$bus.$emit('setName', 'Job')
}
  1. 移除事件
// A.vue
destroyed() {
  this.$bus.$ff('setName')
}

我们在这边只传递了一个事件名参数 setName,这时会移除所有的 setName 监听函数,在实际使用的时候,我们一般会添加第二个参数来指定移除的监听函数

this.$bus.$ff('setName', fn); // fn的指针地址需要和创建($on)添加的函数相同,这点和removeEventListener是一致的

bus使用注意事项

bus模式实际是非常简单的,简单加上日常并不一定需要去使用,以至于我们经常忘记如何使用它。使用的时候有几个需要注意的点

  1. 在组件中使用 this.$bus 之前要先往原型上注册该实例 Vue.prototype.$bus = new Vue()

  2. 在触发事件 this.$bus.$emit(xxx) 之前应该先定义监听函数 this.$bus.$on(xxx, fn)

  3. 在组件销毁的时候,如有必要是需要移除事件的 this.$bus.$off(xxx, fn),不然会多次重复监听

bus模式的原理

知道 订阅发布模式 的一眼就能看出 bus 其实就是一个 订阅发布 的实现。通过 $on 添加订阅,通过 $emit 触发通知广播。关于订阅发布模式可以看看浅谈订阅发布实现vue

所以说,我们的 bus 模式实际是利用了 vue 中的一个 订阅发布 实现,我们通过实例方法 $on$emit$off 来触发 vue 是事件订阅中心。

我们接着来看看其源码部分

  1. $on
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
  const vm: Component = this
  // 可以使用数组同时添加多个订阅
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$on(event[i], fn)
    }
  } else {
    // 订阅:往事件中心vm._events添加事件回调函数fn
    (vm._events[event] || (vm._events[event] = [])).push(fn)
    // ...
  }
  return vm
}
  1. $emit
Vue.prototype.$emit = function (event: string): Component {
  const vm: Component = this
  // 先取事件中心的event类型回调函数
  let cbs = vm._events[event]
  if (cbs) {
    cbs = cbs.length > 1 ? toArray(cbs) : cbs
    const args = toArray(arguments, 1)
    const info = `event handler for "${event}"`
    // 依次触发事件回调函数fn
    for (let i = 0, l = cbs.length; i < l; i++) {
      invokeWithErrorHandling(cbs[i], vm, args, vm, info)
    }
  }
  return vm
}
  1. $off
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
  const vm: Component = this
  // 没有event参数将移除全部
  if (!arguments.length) {
    vm._events = Object.create(null)
    return vm
  }

  // 可移除事件数组
  if (Array.isArray(event)) {
    for (let i = 0, l = event.length; i < l; i++) {
      vm.$off(event[i], fn)
    }
    return vm
  }

  // 没有回调函数则直接退出
  const cbs = vm._events[event]
  if (!cbs) {
    return vm
  }

  // 没有fn参数将移除所有event类型回调
  if (!fn) {
    vm._events[event] = null
    return vm
  }

  // 移除特定事件
  // 可以发现只会移除第一个符合条件的函数
  let cb
  let i = cbs.length
  while (i--) {
    cb = cbs[i]
    if (cb === fn || cb.fn === fn) {
      cbs.splice(i, 1)
      break
    }
  }
  return vm
}

总结

我们今天分析了bus模式的组件通信方式及其实现原理,整体比较简单 good good staduy day day up