Element组件MessageBox剖析

因最近业务需求需要实现类似于Element中的MessageBox组件的效果,所以尝试封装了一个类似的小组件,本文不介绍封装,因为受到MessageBox的启发,所以通过源码注释的方式详细剖析一下Element的MessageBox实现思想。

基础知识

Vue.extend(options)

  • 参数:{Object} options
  • 用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。需要注意的是:data 选项是特例,需要注意 - 在 Vue.extend() 中它必须是函数
<div id="mount-point"></div>
// 创建构造器
var Profile = Vue.extend({
  template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
  data: function () {
    return {
      firstName: 'Walter',
      lastName: 'White',
      alias: 'Heisenberg'
    }
  }
})
// 创建 Profile 实例,并挂载到一个元素上。
new Profile().$mount('#mount-point')

结果:

<p>Walter White aka Heisenberg</p>

为什么要介绍Vue.extend(options),因为Element中MessageBox的使用方式为函数式调用 (this.$confirm())的形式,以这种形式调用组件就不能按照常规的组件引入注册的形式去调用,可以通过Vue.extend(options)创建一个vue子类再通过函数暴露出去的方式实现函数式调用。

Element MessageBox 源码

// main.js 仅介绍主要部分代码
...

import Vue from 'vue';
// 引入模板文件
import msgboxVue from './main.vue'; 
// element合并对象的工具函数,代码比较简单可以自行查阅
import merge from 'element-ui/src/utils/merge'; 
import { isVNode } from 'element-ui/src/utils/vdom';

// 创建一个新的vue构造器,构造器可以手动挂载到一个新的Dom上
const MessageBoxConstructor = Vue.extend(msgboxVue);

let currentMsg, instance;
let msgQueue = [];

// 创建一个新的vue子实例
const initInstance = () => {
  instance = new MessageBoxConstructor({
    el: document.createElement('div')
  });
  // 给实例添加callback对象,后面会分析到
  instance.callback = defaultCallback;
};

// defaultCallback处理了两种形式的回调方式
// 1.可以手动传入一个callback函数
// 2.使用默认的promise方式
const defaultCallback = action => {
  if (currentMsg) {
    let callback = currentMsg.callback;
    // 处理传入回调函数情况
    if (typeof callback === 'function') {
      // showInput区分是否为输入框MessageBox
      if (instance.showInput) {
        callback(instance.inputValue, action);
      } else {
        callback(action, instance);
      }
    }
    // 处理promise情况
    if (currentMsg.resolve) {
      if (action === 'confirm') {
        if (instance.showInput) {
          currentMsg.resolve({ value: instance.inputValue, action });
        } else {
          currentMsg.resolve(action);
        }
      } else if (currentMsg.reject && (action === 'cancel' || action === 'close')) {
        currentMsg.reject(action);
      }
    }
  }
};

const showNextMsg = () => {
  if (!instance) {
    initInstance();
  }
  instance.action = '';

  if (!instance.visible || instance.closeTimer) {
    if (msgQueue.length > 0) {
      // 顺序执行msgQueue中的currentMsg
      currentMsg = msgQueue.shift();
      // currentMsg内容如下:
      // {
      //   options: merge({}, defaults, MessageBox.defaults, options),
      //   callback: callback,
      //   resolve: resolve,
      //   reject: reject
      // }
      let options = currentMsg.options;
      // 将参数挂载到新创建的实例data上
      for (let prop in options) {
        if (options.hasOwnProperty(prop)) {
          // 实例参数修改
          instance[prop] = options[prop];
        }
      }
      
      // 如果options没传入callback将默认的callback赋值给实例的callback
      if (options.callback === undefined) {
        // 当options里面有callback传入,正常输出。
        // 当options里面没有callback,instance.callback使用defaultCallback
        instance.callback = defaultCallback;
      }

      // 再次封装callback
      let oldCb = instance.callback;
      instance.callback = (action, instance) => {
        oldCb(action, instance);
        showNextMsg();
      };

      // 判断message是否传入的是Html片段,如果是Html片段添加到slot
      if (isVNode(instance.message)) {
        instance.$slots.default = [instance.message];
        instance.message = null;
      } else {
        delete instance.$slots.default;
      }
      // 将某些特定的参数设定初始值
      ['modal', 'showClose', 'closeOnClickModal', 'closeOnPressEscape', 'closeOnHashChange'].forEach(prop => {
        if (instance[prop] === undefined) {
          instance[prop] = true;
        }
      });
      // 注意message是挂载到body上
      document.body.appendChild(instance.$el);
      // 控制弹窗出现
      Vue.nextTick(() => {
        instance.visible = true;
      });
    }
  }
};

const MessageBox = function(options, callback) {
  if (Vue.prototype.$isServer) return;
  if (typeof options === 'string' || isVNode(options)) {
    // 当options参数为字符串 this.$msgbox('xxx')情况下默认设置message字段
    options = {
      message: options
    };
    // 若有两个及以上参数判断第二个参数是否为字符串赋值给title
    if (typeof arguments[1] === 'string') {
      options.title = arguments[1];
    }
  } else if (options.callback && !callback) {
    // 参数为对象且对象有callback字段时 将callback赋值给callback
    callback = options.callback;
  }

  // 兼容不支持Promise情况
  if (typeof Promise !== 'undefined') {
    return new Promise((resolve, reject) => { // eslint-disable-line
      msgQueue.push({
        options: merge({}, defaults, MessageBox.defaults, options),
        callback: callback,
        resolve: resolve,
        reject: reject
      });

      showNextMsg();
    });
  } else {
    msgQueue.push({
      options: merge({}, defaults, MessageBox.defaults, options),
      callback: callback
    });

    showNextMsg();
  }
};

  // 使用方式1:
  // this.$msgbox({title:'测试',message:'测试',callback:(action,instance)=>{
  //   console.log(action) //confirm
  //   console.log(instance) //vue实例
  // }})
  // 使用方式2:
  // this.$msgbox({title:'测试',message:'测试'},(action,instance)=>{
  //   console.log(action) //confirm
  //   console.log(instance) //undefined
  // })

...

// 暴露MessageBox方法

export default MessageBox;
export { MessageBox };

可以看出Message组件主要的两个方法一个是MessageBox,一个是showNextMsg,这两个方法的主要功能一个是添加新的message对象一个是设置实例参数,除了callback的理解有些复杂外其他的代码理解应该不难。至于main.vue文件就是普通的vue文件不再赘述。

简单实现

MessageBox组件
源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值