[element-ui源码]element-ui有哪些mixin?

语言: CN / TW / HK

1.回顾mixin基础

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。

1.数据对象在内部会进行递归合并时,当发生冲突时以组件数据优先。

var mixin = {
  data: function () {
    return {
      message: 'mixin',
      foo: 'mixin'
    }
  }
}

new Vue({
  mixins: [mixin],
  data: function () {
    return {
      message: 'goodbye',
      bar: 'def'
    }
  },
  created: function () {
    console.log(this.$data)
    // 输出的是组件数据而不是混入数据
    // => { message: "goodbye", foo: "abc", bar: "def" }
  }
})
复制代码

2.混入对象的钩子将在组件自身钩子之前调用。

var mixin = {
  created: function () {
    console.log('混入对象的钩子被调用')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('组件钩子被调用')
  }
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"
复制代码

3.值为对象的选项,例如 **methods****components** **directives**,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

var mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

var vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () {
      console.log('from self')
    }
  }
})

vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
复制代码

更多详细请查看Vue Mixin

2.element-ui中的mixin

(1)emitter.js

源码:

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
    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
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};
复制代码

解释:

emitter.js中定义的broadcastdispatch两者的用法和vue1.0中的broadcast](https://v1.vuejs.org/api/#vm-broadcast)和[dispatch相似。

先分析**broadcast**方法:

该方法用于往下传播事件,根据给定的组件名称和事件名称,向当前的实例的子组件$children往下递归,寻找对应名称的子组件触发事件。在找到对应的组件时,会停止往下递归。

**broadcast**方法在element-ui中被用的次数也不少,例如在el-autocomplete的部分源码(如下所示):

// packages\autocomplete\src\autocomplete.vue
watch: {
  suggestionVisible(val) {
    let $input = this.getInput();
    if ($input) {
      this.broadcast('ElAutocompleteSuggestions', 'visible', [val, $input.offsetWidth]);
    }
  }
},
复制代码

监听suggestionVisible变量,当发生变化时,通过getInput获取输入框里面的内容,如果内容不为空,则触发子组件ElAutocompleteSuggestions的visible事件。

子组件el-autocomplete-suggestions的部分源码如下所示,在created周期函数中已经监听visible事件,因此当调用该实例的$emit方法触发visible事件时,$on中注册在visible事件下的的回调函数就会被调用。

// packages\autocomplete\src\autocomplete-suggestions.vue
created() {
  this.$on('visible', (val, inputWidth) => {
    this.dropdownWidth = inputWidth + 'px';
    this.showPopper = val;
  });
}
复制代码

现在分析**dispatch**方法:

dispatch方法则是往上传播事件,也是根据给定的组件名称和事件名称,向当前实例的父组件$parent往上递归,寻找对应名称的父组件触发事件。在找到对应的组件时,会停止往上递归。

举一个element-ui中用到dispatch的最直观的需求--表单校验,如图所示:

<el-form-item label="活动名称" prop="name">
  <el-input v-model="ruleForm.name"></el-input>
</el-form-item>
复制代码

在大部分表单组件,例如el-input的源码中,会有以下代码:

watch: {
  value(val) {
    this.$nextTick(this.resizeTextarea);
    if (this.validateEvent) {
      this.dispatch('ElFormItem', 'el.form.change', [val]);
    }
  },
  ...
}
复制代码

当监听到value值变化时,如果属性validateEvent为true,则向当前实例的父组件el-form-item传播'el.form.change'事件以及value值。

而在el-form-item的源码中,有以下代码:

mounted() {
  if (this.prop) {
    ......
    this.addValidateEvents();
  }
},
methods:{
    ...
   addValidateEvents() {
     const rules = this.getRules();

     if (rules.length || this.required !== undefined) {
       this.$on('el.form.blur', this.onFieldBlur);
       this.$on('el.form.change', this.onFieldChange);
     }
   },
    ....
}
复制代码

mounted函数中根据prop是否为真而调用addValidateEvents方法,addValidateEvents中监听el.form.change事件,从而达到父组件因子组件的值的变化而调用rules校验的需求。

(2)focus.js

源码:

export default function(ref) {
  return {
    methods: {
      focus() {
        this.$refs[ref].focus();
      }
    }
  };
};
复制代码

解析:

用于使某个被ref标记的组件进入focus状态,是个简单又通用的方法。

(3)migrating.js

源码:

import { kebabCase } from 'element-ui/src/utils/util';
/**
 * Show migrating guide in browser console.
 *
 * Usage:
 * import Migrating from 'element-ui/src/mixins/migrating';
 *
 * mixins: [Migrating]
 *
 * add getMigratingConfig method for your component.
 *  getMigratingConfig() {
 *    return {
 *      props: {
 *        'allow-no-selection': 'allow-no-selection is removed.',
 *        'selection-mode': 'selection-mode is removed.'
 *      },
 *      events: {
 *        selectionchange: 'selectionchange is renamed to selection-change.'
 *      }
 *    };
 *  },
 */
export default {
  mounted() {
    if (process.env.NODE_ENV === 'production') return;
    if (!this.$vnode) return;
    const { props = {}, events = {} } = this.getMigratingConfig();
    const { data, componentOptions } = this.$vnode;
    const definedProps = data.attrs || {};
    const definedEvents = componentOptions.listeners || {};

    for (let propName in definedProps) {
      propName = kebabCase(propName); // compatible with camel case
      if (props[propName]) {
        console.warn(`[Element Migrating][${this.$options.name}][Attribute]: ${props[propName]}`);
      }
    }

    for (let eventName in definedEvents) {
      eventName = kebabCase(eventName); // compatible with camel case
      if (events[eventName]) {
        console.warn(`[Element Migrating][${this.$options.name}][Event]: ${events[eventName]}`);
      }
    }
  },
  methods: {
    getMigratingConfig() {
      return {
        props: {},
        events: {}
      };
    }
  }
};
复制代码

解释:

此方法用于标记一些已经过期的属性props和事件event,当开发人员在开发模式中用到这些过期的属性或方法时,会通过console.warn在控制台输出提示。

例如在el-input的源码中,有以下代码:

methods:{
  ....
  getMigratingConfig() {
    return {
      props: {
        'icon': 'icon is removed, use suffix-icon / prefix-icon instead.',
        'on-icon-click': 'on-icon-click is removed.'
      },
      events: {
        'click': 'click is removed.'
      }
    };
  },
  ....  
}
复制代码

即,如果在调用el-input时使用了icon,如:<el-input v-model="value" icon="anything">,则控制台会输出警告如下图所示:

(4)vue-popper.js

这个可以说是element-ui的精华。篇幅过长,所以拆成以下两篇文章去分析。

[element-ui源码]element-ui中的神器Popper(使用方法)

[element-ui源码]element-ui中的神器Popper(源码浅析)