在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组件实例,不过由于没有挂载,它仅处于以下阶段:

elementui下载组件 elements组件_elementui下载组件


此时模板还没有被编译为渲染函数。紧接着调用它的$mount方法,现在instance就是一个被完全初始化的组件实例了。

注意,这里在调用$mount的时候并没有传入根节点el,所以组件被挂载后不会真正地渲染到某个DOM节点下面。最后一步,就是执行document.body.appendChild(instance.$el),把整个组件挂到DOM树上的body节点下,使其渲染到页面上。于是,消息组件就显示在页面上了。

为了方便,element-ui还专门为Message构造函数添加了四个静态方法:successwarninginfoerror

['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组件了。