element-ui 源码解析 —— emitter.js

573 阅读1分钟

源码

图片.png

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);
    }
  }
};

解析

  • 核心思想是通过递归或遍历来查找要 broadcast 或 dispatch 的组件的名字,然后在组件自身上 emit 与 on
  • emitter.js 中定义了两个函数:
    1. broadcast(componentName, eventName, params) 广播给子组件(向子组件方向传递),也就是不停的去遍历子节点并且通过 $emit 去触发事件,直到所有的子节点遍历完成之后停止
    2. dispatch(componentName, eventName, params) 分发给父组件(向父组件方向传递),也就是不停的向上去遍历父节点,并通过 $emit 去除法事件,到达根节点之后停止
    图片.png
  • 这两个函数在组件中使用的非常多,所以很多组件中都定义了 "componentName" 自定义属性,便于广播和分发

图片.png

dispatch

dispatch(componentName, eventName, params) {} 方法有三个参数

  • componentName 表示组件名称,用于匹配到正确的组件名
  • eventName 事件名,需要触发的事件
  • params 参数,触发时带入的参数
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;

前两行(上面)用于查找到当前元素的父组件,如果没有就使用根节点,并取父组件的组件名用于后期匹配

// 遍历父组件,查找并匹配 name,直到找到对应的父组件或无
while (parent && (!name || name !== componentName)) {
    parent = parent.$parent;

    if (parent) {
      // 匹配到,则修改 name,用于判断并退出循环
      name = parent.$options.componentName;
    }
}

紧接着是一个 while 循环,用于循环父组件,直到找到,或者到达根元素匹配不到。

在循环中不断使用 parent 变量记录组件名,用于判断是否找到。

其中的 if(parent) 是当有匹配到的父组件时,就去触发父组件的对应的事件函数。

broadcast

broadcast(componentName, eventName, params) {
    broadcast.call(this, componentName, eventName, params);
}
  • .call 可以改变 this 的指向,这里是对 this 进行了纠正,因为 broadcast 函数的 this 并不指向调用者
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]));
    }
  });
}
  • 每条向下传播的线路,只要触发了一次对应的事件,就不会再向下传递了

参考