[toc]

# 1. 开始

组内很多项目是Vue写的,之前页面调用弹窗很麻烦,要引入弹窗组件,在data中声明showDialog,在methods中声明onShowDialogonCloseDialog等至少两个方法,然后在template中还要写:

<Dialog 
  v-if="showDialog" 
  :data="someData"
  @onCloseDialog="onCloseDialog" 
/>

可以看到引入一个弹窗要加这么多东西,既费劲又不容易维护。

# 2. 原理

# 2.1. H5

H5的核心是利用了Vue.extend,将弹窗组件作为构造器,生成一个子类 (opens new window)。核心代码如下:

function initInstance() {
  const dialogId = 'press-dialog';
  const oldDialog = document.getElementById(dialogId);
  if (oldDialog) {
    oldDialog.parentNode.removeChild(oldDialog);
  }
  const dialogRootDiv = document.createElement('div');
  dialogRootDiv.id = dialogId;

  document.body.appendChild(dialogRootDiv);

  const instance = new (Vue.extend(VueDialog))({
    el: dialogRootDiv,
  });
  return instance;
}

# 2.2. 小程序

uni-app版本的小程序可以例用selectComponent获取实例,核心代码如下:

const context = options.context || getContext();
const dialog = context.selectComponent(options.selector);

# 2.3. Promise

此外要让组件支持Promise的函数调用,即点击确定进入then方法,点击取消进入catch,有两种方案:

# 2.3.1. 组件内部声明showDialog方法

handler.js中:

let instance;
const Dialog = (options) => {
  instance = getContext().selectComponent('#press-dialog');

  return instance.showDialog(options).then((val) => {
    instance = null;
    return Promise.resolve(val);
  })
    .catch((err) => {
      instance = null;
      return Promise.reject(err);
    });
};

组件中:

data() {
   return {
    resolve: '',
    reject: '',
    promise: '',

    show: false,
    title: t('dialog.title'),
    content: '',
  };
}
methods: {
  // 弹出messageBox,并创建promise对象
  showDialog(options) {
    this.show = true;

    Object.keys(options).map((key) => {
      this[key] = options[key];
    });

    this.promise = new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    });

    // 返回promise对象
    return this.promise;
  }
}

这种方法的缺点是,组件内要写重复的showDialog方法,还要在data中声明promise、resolve、reject等。

# 2.3.2. handler中动态注入callback

handler.js中:

const Dialog = (options) => {
  return new Promise((resolve, reject) => {
    const dialog = getContext().selectComponent(options.selector);
    if (dialog) {
      const newOptions = {
        callback: (action, instance) => {
          action === 'confirm' ? resolve(instance) : reject(instance);
        },
        ...options,
      };
      dialog.setData(newOptions);

      Vue.nextTick(() => {
        dialog.setData({ show: true });
      });
    }
  });
};

组件中:

methods: {
  close(action) {
    this.dataShow = false;
    this.$nextTick(() => {
      this.$emit('close', action);
      const { callback } = this;
      if (callback) {
        callback(action, this);
      }
    });
  },
}

这种方法的缺点就是逻辑比较绕,相对难理解些。