优雅使用el-dialog组件

10,501 阅读2分钟

在使用vue做业务的时候经常会用到弹框,最快的办法是使用优秀组件库,比如Element-ui中的el-dialog,使用起来也很方便 大概有如下几种做法:

方法一:将dialog和content-body放在一起

child.vue

<el-dialog
  title="提示"
  :visible.sync="visible"
  width="30%"
  :before-close="handleClose">
  <span>这是一段信息</span>
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
  </span>
</el-dialog>

parent.vue

<child 
    :visible="visible" 
    :otherData="otherData"/>

与此同时需要在当前页面写入visible,并且要在关闭的时候清空数据。 (PS:由于外层传入的prop,内部不能进行修改,如果要修改的话要emit到父级进行同步更新)

<child 
+   :visible.sync="visible" 
    :otherData="otherData"/>
close() {
    this.$emit('update:visible', false);
}

方法二:将外层dialog放在parent中,抽象content-body内容为一个组件进行导入

child.vue

<span>这是一段内容</span>

parent.vue

<el-dialog
  title="提示"
  :visible.sync="visible"
  width="30%"
  :before-close="handleClose">
  <child />
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
  </span>
</el-dialog>

parent组件中需要定义visible属性,并且要在关闭的时候清空数据

child.vue

resetFields() {
    // 清空child数据
    Object.assign(this.$data,this.$options.data())
}

parent.vue

handleClose() {
    this.visible = false;
    this.$refs['child'].resetFields();
}

方法三:结合Vuex状态管理,这样不需要通过attrs或者props传入进行通信,全局维护一个状态

child.js

const viewmodel = {
    state: {
        visible: false
    },
    mutations: {
        setVisible(state, payload) {
            state.visible = payload;
        }
    }
}
<el-dialog
  title="提示"
  :visible.sync="visible"
  width="30%"
  :before-close="handleClose">
  <child />
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
  </span>
</el-dialog>

...mapState('child', ['visible'])
...mapMutation('child', ['setVisible'])

以上一些思路都让人感觉比较冗余,用是可以用的,但不推荐。如果一个页面有多个弹框的时候就感觉不够优雅,所以需要进行一定的封装。

首先对于一个dialog组件而言,他无非是需要visible来控制显示/隐藏title来控制弹框标题,content来表示内容区,close来代表外层调用关闭弹框。

props: {
    title: {
        type: String,
        default: ''
    },
    component: {
        type: Function,
        default: () => {}
    },
    close: {
        type: Function,
        default: () => {}
    }
}
methods: {
    closeDialog() {
      // 清空弹框数据
      Object.assign(this.$data, this.$options.data());
      // 调用props中的close方法清空数据
      this.close();
      // 销毁组件,去掉watcher监听
      this.$refs.component.$destroy();
    }
}

我们定义一个customDialog组件,内部定义data属性visible

  1. 我们通过extend生成一个新的sub方法,将sub指向父级原型链,将传入属性merge到sub下的options中,继承父级原型方法,并将sub中的super指向父级对象。
  2. 其次对这个方法进行new操作,本质是生成新的vue实例,会经过一系列的init操作。
  3. 然后对这个vue实例进行$mount操作,本质是进行虚拟dom的patch操作,返回el对象
  4. 最后就可以将这个el对象append到body下,就完成了弹框组件的渲染
const DialogConstructor = Vue.extend(CustomDialog);
inst = new DialogConstructor();
inst.visible = true;
inst.$mount();
document.body.appendChild(inst.$el);

问题1:如何能够从外层改变内部弹框属性

通过传入propsData属性

+ const createDialog = propsData => {
    const DialogConstructor = Vue.extend(CustomDialog);
    inst = new DialogConstructor({
+       propsData
    });
    inst.visible = true;
    inst.$mount();
    document.body.appendChild(inst.$el);
+ }

这样我们就可以通过createDialog({title: xxx})来构造不同的弹框

问题2:打开过的弹框,再次打开有需要再次进行构造

将已经打开的弹框实例依次存入一个map中,通过设置唯一的uid值来进行获取

const createDialog = () => {
+   const instance = {};
    return propsData => {
        const uid = propsData.id || propsData.title;
        let inst = instance[uid];
        if (inst) {
+           inst.visible = true;
        } else {
            ...
+           instance[uid] = inst;
        }
+       return inst;
    }
}

除此之外需要如果当前实例propsData有经过更新,则需要对实例进行更新

const createDialog = () => {
    return propsData => {
        const instance = {};
        if(inst) {
            inst.visible = true;
+           const originPropsData = mapValues(
+               inst.$options.props,
+               value => value.default
+           );
+           Object.assign(inst.$props, originPropsData, propsData);
        }
    }
}

问题3:如何让弹框按打开顺序正确关闭

可以将已打开的实例对象存放在一个栈里。每次打开,其实是将实例入栈;每次关闭,其实是将实例出栈。设置一个$closeDialog方法挂到Vue原型链上,主要处理是获取当前实例,调用当前实例的close方法,将当前实例出栈。

const createDialog = () => {
    const instance = {};
+   const openedDialogQueue = [];
    return propsData => {
        const uid = propsData.id || propsData.title;
        let inst = instance[uid];
+       Vue.prototype.$closeDialog = () => {
+         const currentInst = openedDialogQueue.pop();
+         currentInst && currentInst.closeDialog();
+       };
        if (inst) {
          inst.visible = true;
          // NOTE: 更新props
          const originPropsData = mapValues(
            inst.$options.props,
            value => value.default
          );
          Object.assign(inst.$props, originPropsData, propsData);
          // NOTE: 主要处理多个弹框打开后关闭的顺序
+         openedDialogQueue.push(inst);
        } else {
          const DialogConstructor = Vue.extend(CustomDialog);
          inst = new DialogConstructor({
            propsData
          });
          inst.visible = true;
          inst.$mount();
          document.body.appendChild(inst.$el);
          openedDialogQueue.push(inst);
          instance[uid] = inst;
        }

        return inst;
    };
}

这样一个简单的弹框就实现,以后每次使用只需要写上简短的几行代码既可以搞定

this.$dialog({
    id: 'key',
    title: '弹框',
    component: () => <div>Hello, world!</div>,
})

总结

以上是自己学习网上大佬后总结一些的心得,在一定程度上提高了开发效率。生活和工作中都需要总结,写下这篇文章,一方面为了记录自己的所做所思,另一方面分享给各位一起学习,如果有什么不正确的地方,希望得到大牛们的指正,万分感谢!

custom-dialog