在 Vue 3 中,我们可以使用函数声明方式和命令式方式来封装弹框组件,以实现在应用中方便地显示弹框。本文将介绍如何使用这两种方式来封装一个简单的弹框组件,并提供示例代码和用法说明。
1.函数声明式
先创建子组件dialog.vue
子组件代码
// dialog.vue
<template>
<div class="mask" v-if="show">
<div class="modal-content">
<div class="dialog-header">{{ title }}</div>
<div class="dialog-body">{{ content }}</div>
<div class="dialog-footer">
<el-button @click="onConfirm" type="primary">{{ confirmText }}</el-button>
<el-button @click="onCancel">{{ cancelText }}</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
show: {
type: Boolean,
default: false
},
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
confirmText: {
type: String,
default: '确定'
},
cancelText: {
type: String,
default: '取消'
}
})
const emit = defineEmits(['confirm', 'cancel', 'update:show'])
const onConfirm = () => {
emit('update:show', false)
emit('confirm')
}
const onCancel = () => {
emit('update:show', false)
emit('cancel')
}
</script>
<style scoped lang="scss">
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
.modal-content {
box-shadow:
0px 12px 32px 4px rgba(0, 0, 0, 0.04),
0px 8px 20px rgba(0, 0, 0, 0.08);
background: #fff;
padding: 16px;
position: relative;
width: 500px;
border-radius: 2px;
margin: 140px auto 0;
}
.dialog-header {
padding-bottom: 16px;
color: #303133;
font-size: 18px;
font-weight: 500;
line-height: 24px;
}
.dialog-body {
color: 606266;
font-size: 14px;
}
.dialog-footer {
margin-top: 16px;
text-align: right;
}
}
</style>
父组件代码
父组件调用
<template>
<div>
<el-button @click="showModal2">提示2</el-button>
<Dialog
v-model:show="showDialog"
:title="'提示'"
:content="'这是一段内容'"
@confirm="confirm"
@cancel="cancel"
></Dialog>
</div>
</template>
<script setup lang="ts">
import Dialog from './dialog.vue'
const showDialog = ref(false)
const showModal2 = () => {
showDialog.value = true
}
const confirm = () => {
console.log('确定')
}
const cancel = () => {
console.log('取消')
}
</script>
<style scoped lang="scss"></style>
效果展示
2.命令式
有时候我们不想在模板文件中再次引入子组件,并且传递参数给子组件,我们想要像elementPlus那样,可以直接调用ElMessageBox.alert(),并且可以使用.then和.catch,那我们就需要使用命令式声明一个弹框组件。这种方式是通过动态的创建元素和销毁元素来实现,点击按钮创建弹框并append到body里面,这里需要用到挂载,可参考main.ts的写法
// main.ts
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
下面的js文件也是一样,我们引入弹框组件dialog.vue ,import Dialog from './dialog.vue'
,然后const app = createApp(Dialog),createApp的第二个参数接受一个options,可参考官方API。同时为了避免影响其他元素样式,把它append到body里面,最后挂载app.mount()。点击关闭按钮,销毁弹框元素,卸载组件,app.unmount()。
首先还是先创建dialog.vue,代码如下
<template>
<div class="mask">
<div class="modal-content">
<div class="dialog-header">{{ title }}</div>
<div class="dialog-body">{{ content }}</div>
<div class="dialog-footer">
<el-button @click="onConfirm" type="primary">{{ confirmText }}</el-button>
<el-button @click="onCancel">{{ cancelText }}</el-button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
title: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
confirmText: {
type: String,
default: '确定'
},
cancelText: {
type: String,
default: '取消'
},
onConfirm: {
type: Function,
default: () => {}
},
onCancel: {
type: Function,
default: () => {}
}
})
</script>
<style scoped lang="scss">
.mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
.modal-content {
box-shadow:
0px 12px 32px 4px rgba(0, 0, 0, 0.04),
0px 8px 20px rgba(0, 0, 0, 0.08);
background: #fff;
padding: 16px;
position: relative;
width: 500px;
border-radius: 2px;
margin: 140px auto 0;
}
.dialog-header {
padding-bottom: 16px;
color: #303133;
font-size: 18px;
font-weight: 500;
line-height: 24px;
}
.dialog-body {
color: 606266;
font-size: 14px;
}
.dialog-footer {
margin-top: 16px;
text-align: right;
}
}
</style>
跟函数声明方式的差别是:props里面多了onConfirm和onCancel。
然后创建dialog.js,代码如下:
import { createApp } from 'vue'
import Dialog from './dialog.vue'
function openModal({ title, content, confirmBtnTxt = '确定', cancelBtnTxt = '取消' }) {
return new Promise((resolve, reject) => {
const app = createApp(Dialog, {
title,
content,
confirmBtnTxt,
cancelBtnTxt,
onConfirm: () => {
unmount()
resolve()
},
onCancel: () => {
unmount()
reject(new Error())
}
})
// 创建一个挂载容器
const parentNode = document.createElement('div')
document.body.appendChild(parentNode)
// 卸载组件
const unmount = () => {
app.unmount()
document.body.removeChild(parentNode)
}
// 挂载组件
app.mount(parentNode)
})
}
export default openModal
这时候组件已经创建完成了,看如何使用呢?
如何使用
<template>
<div>
<el-button @click="showModal">提示</el-button>
</div>
</template>
<script setup lang="ts">
import openModal from './dialog.js'
const showModal = () => {
openModal({
title: '提示',
content: '这是一段内容'
})
.then(() => {
console.log('确定')
})
.catch(() => {
console.log('取消')
})
}
</script>
<style scoped lang="scss"></style>
3.jsx封装弹框组件
上面的命令式封装方法,需要写2个文件,.vue和.js,jsx只需要一个文件即可。
具体代码和上面的命令式差不多,区别是要使用jsx语法,在使用jsx语法前,要先安装一个插件@vitejs/plugin-vue-jsx,否则会报错
npm i @vitejs/plugin-vue-jsx
然后再vite.config.ts中引入,
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [
vue(),
vueJsx()
],
})
创建dialog.jsx
// dialog.jsx
import { createApp, defineComponent } from 'vue'
import './modal.scss'
let app, parentNode
const ModalComponent = defineComponent({
name: 'Modal',
props: {
title: {
type: String,
default: 'Modal'
},
onConfirm: {
type: Function,
default: () => {},
required: true
},
onClose: {
type: Function,
default: () => {},
required: true
}
},
setup(props) {
const handleConfirm = () => {
props.onConfirm()
closeModal()
}
const handleClose = () => {
props.onClose()
closeModal()
}
const closeModal = () => {
app.unmount()
document.body.removeChild(parentNode)
}
return {
handleConfirm,
handleClose,
closeModal
}
},
render() {
return (
<div class="modal">
<div class="modal-content">
<h2>{this.title}</h2>
<button onClick={this.handleConfirm}>确定</button>
<button onClick={this.handleClose}>关闭</button>
</div>
</div>
)
}
})
export function openModal({ title, onConfirm, onClose }) {
app = createApp(ModalComponent, {
title,
onConfirm,
onClose
})
parentNode = document.createElement('div')
document.body.appendChild(parentNode)
app.mount(parentNode)
}
// modal.scss
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
.modal-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
h2 {
margin-bottom: 10px;
}
button {
margin-top: 20px;
padding: 8px 16px;
border: none;
background-color: #007bff;
color: #fff;
border-radius: 4px;
cursor: pointer;
&:hover {
background-color: #0056b3;
}
}
}
}
如何使用
<template>
<div>
<el-button @click="showModal">tsx提示</el-button>
</div>
</template>
<script setup lang="ts">
import { openModal } from './dialog.jsx'
const showModal = () => {
openModal({
title: '这是一个弹框',
onConfirm: () => {
console.log('点击了确定按钮')
},
onClose: () => {
console.log('点击了关闭按钮')
}
})
}
</script>
<style scoped lang="scss"></style>