Vue中封装制作一个可复用的模态框组件

2,741 阅读4分钟

在浏览网页的过程中,模态框其实并不陌生。

不过在vue开发过程中,应当如何封装一个vue组件为可以复用的模态框呢?

1,思路

首先我们不难发现,模态框有以下特点:

  • 有一个半透明蒙层,防止用户没有做完模态框的命令就操作其它内容
  • 显示在最顶层,这说明很多模态框是直接放在body元素中的

我先开始就想,能不能先在vue文件中把模态框样式定义好,然后利用props或者slot传值、利用refs调用其方法呢?后来发现这样不仅不方便(每次都要注册组件)、还无法把模态框直接放在body里面。

参考了官方文档,我发现其实再写一个配套的js文件,在js文件中定义显示模态框的函数,借助这个js在body元素下创建一个元素,并将vue组件挂载上去即可。下面我们就一一来实现。

2,编写vue文件,完成模态框基本样式

首先需要完成一些对话框的基本样式,我在components目录下创建vue文件,内容如下:

<template>
  <div class="mydialog">
    <div class="frame">
      <div class="content">
        <img :src="image" />
        <div class="text">{{ text }}</div>
      </div>
      <div class="buttons">
        <div class="ok" @click="clickOk">确定</div>
        <div class="cancel" @click="clickCancel">取消</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'mydialog',
  data() {
    return {
      /**
       * 显示文字
       */
      text: '',
      /**
       * 显示图片
       */
      image: '',
      /**
       * 模态框挂载的DOM元素,用于后面销毁模态框
       */
      mountDom: '',
    };
  },
  methods: {
    /**
     * 自定义确定事件
     */
    ok() {},
    /**
     * 自定义取消事件
     */
    cancel() {},
    clickOk() {
      // 先执行自定义确定方法
      this.ok();
      // js传入该模态框实例挂载的元素,关闭时将其销毁
      this.mountDom.remove();
    },
    clickCancel() {
      // 先执行自定义取消方法
      this.cancel();
      // js传入该模态框实例挂载的元素,关闭时将其销毁
      this.mountDom.remove();
    },
  },
};
</script>

<style lang="scss" scoped>
.mydialog {
  position: absolute;
  width: 100vw;
  height: 100vh;
  left: 0;
  top: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: rgba(255, 255, 255, 0.7);

  .frame {
    width: 560px;
    height: 250px;
    background-color: rgb(177, 255, 229);
    box-shadow: 1px 1px 7px 2px rgb(90, 203, 255);
    border-radius: 5px;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    align-items: center;

    .content {
      display: flex;
      width: 85%;
      justify-content: flex-start;
      align-items: center;

      img {
        height: 64px;
      }

      .text {
        font-size: 24px;
        margin-left: 24px;
      }
    }

    .buttons {
      display: flex;
      width: 80%;
      justify-content: space-around;
      align-items: center;
      font-size: 26px;

      .ok,
      .cancel {
        width: 75px;
        height: 32px;
        text-align: center;
        line-height: 32px;
        border-radius: 6px;
        user-select: none;
        cursor: pointer;
      }

      .ok {
        &:hover {
          color: white;
          background-color: blue;
        }
      }

      .cancel {
        &:hover {
          color: white;
          background-color: rgb(255, 0, 98);
        }
      }
    }
  }
}
</style>

这里重点是在data()中我定义了一些变量,用于我们可以自定义模态框内容。

问题是,模态框这么销毁自己呢?就算是使用v-if关闭了自己,但是前面提到了,这个模态框是要挂载至一个元素中的,这个挂在元素无法被消除怎么做呢?

所以可以看到上面还有一个mountDom变量,让js创建挂载元素之后将其传入,这样vue就可以直接销毁这个挂载元素了!

除此之外,还预留了okcancel两个函数用于按下“确定”和“取消”之后的自定义事件,传入自定义的函数赋值给okcancel即可。

3,编写js文件,封装方法以供调用,给模态框组件传参

在同级目录下创建js文件,内容如下:

import {
	createApp
} from 'vue';

import MyDialog from './MyDialog.vue';

/**
 * 显示自定义对话框
 * @param {String} text 对话框提示内容 
 * @param {NodeRequire} image 对话框图标(需要require图片路径)
 * @param {Function} ok 自定义确定事件
 * @param {Function} cancel 自定义取消事件
 */
export function showDialog(text, image, ok, cancel) {
	// 创建一个div元素放到body下面,供模态框vue组件挂载
	let mountDom = document.createElement('div');
	mountDom.style.position = 'absolute';
	mountDom.style.left = 0;
	mountDom.style.top = 0;
	document.body.appendChild(mountDom);
	// 挂载组件以显示,mount函数返回vue组件实例
	let dialog = createApp(MyDialog).mount(mountDom);
	// 设定vue实例中的变量,将一些数据传入vue
	dialog.text = text;
	dialog.image = image;
	dialog.ok = ok;
	dialog.cancel = cancel;
	// 传入挂载的dom是为了vue组件关闭时可以直接销毁这个挂载的dom达到销毁模态框的目的
	dialog.mountDom = mountDom;
}

可见这里就很简单了。定义一个函数,先创建一个div元素加到body中用于挂载对话框组件,然后利用createApp函数挂载这个对话框组件至创建的div元素之下即可。

看到这,大家也知道了怎么在js中,向vue实例传递值或者调用函数了。

在官方文档中,对于createApp函数,有这样一句话:

与大多数应用方法不同的是,mount不返回应用本身。相反,它返回的是根组件实例。

image.png

我们知道,每一个vue组件可以被作为一个实例使用,那么mount函数,不仅仅是挂载了组件至指定的元素下,还会返回其实例,我们也可以调用这个实例中的变量、函数,实现传递参数的目的。

mount函数中,可以是dom元素实例,也可以是字符串形式的选择器(例如'#app',挂载到id为app的元素下)。

4,测试

现在我们在根组件里面写一个按钮并调用试试:

<template>
  <div class="app">
    <button @click="testShowDialog">显示模态框</button>
  </div>
</template>

<script>
// 先引入js中显示对话框函数
import { showDialog } from './components/mydialog.js';

export default {
  name: 'app',
  methods: {
    testShowDialog() {
      showDialog(
        '提示:少时诵诗书所所所所所所所所所所所所所所所所所所所所所所所所所所所所所所所',
        require('./assets/1.png'),
        () => {
          console.log('点击了确定');
        },
        () => {
          console.log('点击了取消');
        }
      );
    },
  },
};
</script>

效果:

image.png

我们来看看html中元素:

image.png

再结合一下上面的js文件看看,相信能够理解这个思路。

5,总结

可见封装一个可复用的vue组件并不难,先写好样式,再写一个配套的js文件给其传参、用于调用即可。其实封装其它类型可复用组件,也是一样的思路。

示例代码仓库地址