说明:本文基于[email protected],源码详见element。
src/mixins/emitter.js中有两个方法:dispatch
和broadcast
,这两个方法在多处用到:
例如,在packages/select/src/select.vue中,value的监控方法中用到dispatch
和broadcast
:
watch: {
value(val, oldVal) {
if (!valueEquals(val, oldVal)) {
this.dispatch('ElFormItem', 'el.form.change', val);
}
},
visible(val) {
if (!val) {
this.broadcast('ElSelectDropdown', 'destroyPopper');
}
}
}
一、dispatch(子组件发送消息给上层组件)
dispatch(componentName, eventName, params) {
// 当前父组件
var parent = this.$parent || this.$root;
// 当前父组件的组件名
var name = parent.$options.componentName;
// 通过$parent,一直向上找,直到组件名等于componentName
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
// 如果找到目标组件,那么调用目标组件的$emit方法
parent.$emit.apply(parent, [eventName].concat(params));
}
}
还是以packages/select/src/select.vue为例:
el-select组件中调用dispatch
方法(this.dispatch('ElFormItem', 'el.form.change', val)
),通过while循环,找到上层名为ElFormItem的组件,并在上层组件实例中$emit el.form.change
事件,最后,在packages/form/src/form-item.vue
中,通过$on方法捕获该事件:
addValidateEvents() {
......
if (rules.length || this.required !== undefined) {
......
this.$on('el.form.change', this.onFieldChange);
}
}
二、broadcast(上层组件通知下层组件)
function broadcast(componentName, eventName, params) {
// 遍历所有子组件
this.$children.forEach(child => {
var name = child.$options.componentName;
// 找到组件名为componentName的子组件,并调用该子组件的$emit方法;
// 否则,继续递归
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
还是以packages/select/src/select.vue为例:
el-select组件中调用broadcast方法:
handleQueryChange(val) {
......
this.broadcast('ElOption', 'queryChange', val);
......
}
上面代码,会向所有el-option子组件广播queryChange事件并携带数据,在el-option组件的created方法中会通过$on方法响应:
created() {
......
this.$on('queryChange', this.queryChange);
......
}
三、src/mixins/migrating.js:对发生改动的props或eventName发出警告
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: {}
};
}
}
};
其中,kebabCase
方法的作用是将类似abCd
这种格式转换成ab-cd
,也就是驼峰形式转成短横线连接的形式。
上面的mounted和getMigratingConfig都会混入各个组件之中,以packages/autocomplete/src/autocomplete.vue
为例:
在该文件中,实现了getMigratingConfig
方法:
getMigratingConfig() {
return {
props: {
'custom-item': 'custom-item is removed, use scoped slot instead.',
'props': 'props is removed, use value-key instead.'
}
};
},
在非生产环境中,当加载了el-autocomplete组件后,就会爆出两条警告:
custom-item is removed, use scoped slot instead.
props is removed, use value-key instead.
推荐
ElementUI的结构与源码研究
elementUI——locale,国际化方案
elementUI——directives:mousewheel & repeat-click
elementU——transitions
elementUI——主题