element-ui - 原始碼學習 - 自定義事件

moreor發表於2018-07-31

1.前言

這次探索源於改造element-ui中下拉選單的觸發方式,el-dropdown提供了兩種觸發方式hoverclick。由於之前專案中有很多自定義右鍵選單的操作。而element-ui並未提供右鍵選單相關的元件,於是檢視el-dropdown的原始碼想改造一個支援此右鍵操作的下拉選單。儘管這不符合實際使用者操作習慣哈,不過這只是一次改造element-ui元件的一個嘗試,改造並非止於此。

2.el-dropdown目錄結構

element-ui - 原始碼學習 - 自定義事件
檢視dropdown.vuedropdown-item.vue中都混入了Emitter

import Emitter from 'element-ui/src/mixins/emitter';

3. Emitter

在學習vue自定義事件的時候,父元件在使用子元件時,可以為子元件的自定義事件指定事件處理程式。子元件中可以通過vm.$emit(eventName, [...args])觸發自定義事件。但是vue官網的案例比較簡單,案例中只涉及到一層子元件。

Emitter定義了兩個方法,擴充套件了自定義事件,父元件可以通過broadcast(廣播)的方式觸發後代元件的自定義事件;後代元件可以通過dispatch(派遣)的方式觸發最近一級指定名稱的祖先元件的自定義事件。原始碼如下:

// 指定後代元件名稱componentName
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;
      // 迴圈找到最近一級名稱為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);
    }
  }
};
複製程式碼

4.案例

<el-dropdown trigger="click">
  <span class="el-dropdown-link">
    下拉選單<i class="el-icon-arrow-down el-icon--right"></i>
  </span>
  <el-dropdown-menu slot="dropdown">
    <el-dropdown-item>黃金糕</el-dropdown-item>
    <el-dropdown-item>獅子頭</el-dropdown-item>
    <el-dropdown-item>螺螄粉</el-dropdown-item>
    <el-dropdown-item>雙皮奶</el-dropdown-item>
    <el-dropdown-item>蚵仔煎</el-dropdown-item>
  </el-dropdown-menu>
</el-dropdown>
複製程式碼

從元件程式碼結果上看,el-dropdown子元件是el-dropdown-item,孫元件是el-dropdown-item。檢視el-dropdown的偵聽器:

watch: {
  visible(val) {
    this.broadcast('ElDropdownMenu', 'visible', val);
    this.$emit('visible-change', val);
  },
  focusing(val) {
    const selfDefine = this.$el.querySelector('.el-dropdown-selfdefine');
    if (selfDefine) { // 自定義
      if (val) {
        selfDefine.className += ' focusing';
      } else {
        selfDefine.className = selfDefine.className.replace('focusing', '');
      }
    }
  }
}
複製程式碼

this.broadcast('ElDropdownMenu', 'visible', val);用於向後代名為ElDropdownMenu呼叫名為visible的自定義事件。

在檢視dropdown-item.vue選項中的方法:

methods: {
  handleClick(e) {
    this.dispatch('ElDropdown', 'menu-item-click', [this.command, this]);
  }
}
複製程式碼

this.dispatch('ElDropdown', 'menu-item-click', [this.command, this]);觸發了el-dropdown的自定義事件menu-item-click

5.總結

Emitter實際上擴充套件了自定義事件的觸發範圍。父元件可以觸發後代元件的自定義事件,不限於兒子元件。後代元件可以觸發最近一級指定名稱的祖先元件的自定義事件。其中涉及到事件處理程式的位置,通俗的講,就是父元件幹了一件事情,它可以通過廣播的方式告訴後代元件作出迴應,事件處理程式在後代元件中定義;而後代元件幹了一件事件,可以通知某一祖先元件作出迴應,事件處理程式在祖先元件中定義。

相關文章