在element-ui的组件中,有些组件的用法看起来比较特别,如message组件,它不是像别的组件一样写在模板中,而是通过js的方式调用的。
在注册完element组件后,一般的组件是这样用的:
<template>
<el-button>点击我</el-button>
</template>
但是message组件则是这样使用的:
...
this.$message.success('保存成功');
...
这样就会在页面上弹出一个成功的消息提示框。
那么这类组件是怎么实现的呢?
首先,我们打开node_modules
下的element-ui
文件夹,这里是element-ui库所在的位置。src/index.js是项目的入口文件,它内部定义了element-ui的安装方法install
:
const install = function(Vue, opts = {}) {
...
// 注册普通组件
components.forEach(component => {
Vue.component(component.name, component);
});
...
// 注册需要用js调用的组件
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
};
我们看到,所有可以使用js方法调用的组件都是使用Vue.prototype.$xxx = xxx
的格式注册到Vue的原型对象上的。由于在Vue2.x中,根组件是Vue构造函数的实例,而Vue在创建子组件时,会把根组件的原型方法添加到子组件上。所以当上述代码执行之后,所有组件实例都可以通过原型链访问这些组件,也就是说:
let comp = new Vue();
comp.$message; // Message
// 在子组件内
this.$message; // Message
接着我们打开packages
文件夹,来看Message的实现。找到element-ui/packages/message
文件夹,先来看它的入口文件index.js
:
import Message from './src/main.js';
export default Message;
顺着找到这里引用的main.js
(注意是当前目录下的src):
import Vue from 'vue';
import Main from './main.vue';
...
let MessageConstructor = Vue.extend(Main);
let instance;
...
const Message = function(options) {
...
instance = new MessageConstructor({
data: options
});
...
instance.$mount();
document.body.appendChild(instance.$el);
}
['success', 'warning', 'info', 'error'].forEach(type => {
Message[type] = options => {
if (typeof options === 'string') {
options = {
message: options
};
}
options.type = type;
return Message(options);
};
});
这里只列出了关键代码。首先导入Vue和Main,我们打开同目录下的Main.vue
,可以看到这就是Message组件的实现,和我们平时写的组件没什么差别,这里不再讨论。
接着,使用Vue.extend
全局方法创建了一个继承自Vue的构造函数MessageConstructor
,以Main组件为参数。这样,当调用new MessageContructor
时就会创建一个Message组件实例。
接下来就是Message构造函数的实现了。它会调用MessageContructor
构造一个Message组件实例,不过由于没有挂载,它仅处于以下阶段:
此时模板还没有被编译为渲染函数。紧接着调用它的$mount
方法,现在instance
就是一个被完全初始化的组件实例了。
注意,这里在调用$mount
的时候并没有传入根节点el
,所以组件被挂载后不会真正地渲染到某个DOM节点下面。最后一步,就是执行document.body.appendChild(instance.$el)
,把整个组件挂到DOM树上的body节点下,使其渲染到页面上。于是,消息组件就显示在页面上了。
为了方便,element-ui还专门为Message构造函数添加了四个静态方法:success
、warning
、info
和error
:
['success', 'warning', 'info', 'error'].forEach(type => {
Message[type] = options => {
if (typeof options === 'string') {
options = {
message: options
};
}
options.type = type;
return Message(options);
};
});
现在Message.success
就等价于Message(Object.assign(options, {type: 'success'}))
。
现在你就可以用下面的方式来调用Message组件了:
this.$message({type: 'success', message: '消息组件'});
this.$message.success('消息组件');
Vue.prototype.$message({type: 'success', message: '消息组件'});
Vue.prototype.$message.success('消息组件');
可能后面两种方法我们平时不常用,不过如果你想在Vuex中使用Message组件,很可能需要用到它:
messageModule.js
import Vue from 'vue';
const messageModule = {
state () {
...
},
mutations: {
setMessage (state, message) {
Vue.prototype.$message.success('在Vuex中使用Message!');
}
}
}
export default messageModule;
这里如果使用this.$message
是无法访问Message组件的。因为虽然Vuex的store本身也是一个Vue实例,但是它的每个module并不是Vue实例,并且Vuex在创建module的时候不会像Vue创建组件那样把根实例的属性都复制到每个module上,所以module实例的属性并不包含$message
,因此也就无法调用Message组件。这时候通过直接引用Vue的原型对象,就可以直接访问到Message组件了。