实现 ElementPlus 的 MessageBox
ElementPlus 的 MessageBox 主要功能分析提供一个函数用来展示消息框,这个函数提供如标题、内容等配置参数消息框出现和消失时有动画使用 Promise 获取消息框的结果基本思路Vue 中动态显示一个组件,无非就是通过 h 函数创建 VNode,并且把这个 VNode 挂载在 DOM 树上。这里有两种挂载的方式:createApp在 main.js 中创建 Vue 实例用的就是这种
ElementPlus 的 MessageBox 主要功能分析
- 提供一个函数用来展示消息框,这个函数提供如标题、内容等配置参数
- 消息框出现和消失时有动画
- 使用 Promise 获取消息框的结果
基本思路
Vue 中动态显示一个组件,无非就是通过 h 函数创建 VNode,并且把这个 VNode 挂载在 DOM 树上。这里有两种挂载的方式:
createApp
在 main.js 中创建 Vue 实例用的就是这种方法,这也是 Vue3 中代替 Vue2 的 Vue.extend 的方法,简单使用案例如下:
const app = createApp(MessageBox, {
message: 'hello?'
})
// 创建无父元素的文档对象,挂载到 DOM 中后直接调用 app.unmount() 移除 MessageBox
// 和挂载到 div 的区别是 MessageBox 不会作为 div 的子元素
const frg = document.createDocumentFragment()
// app.mount 返回组件实例
// 组件实例内包含 expose 出来的方法或者数据
const vm = app.mount(frg)
document.body.appendChild(frg)
h + render
和 createApp 方法大同小异
const vn = h(MessageBox, {
message: 'vnode'
})
render(vn, container)
document.body.appendChild(container)
可以看到无论是 createApp 方法还是 h 方法,都可以在第二个参数中传入组件的 props,于是我们可以封装一个动态显示组件的函数,这个函数接受组件的 props。但是在封装函数之前,让我们先来实现 MessageBox 这个组件
MessageBox 组件实现
直接贴代码,讲下最关键的几点:
进入退出动态效果实现
设置一个 transition flag,初始时为 false,组件 mounted 后 flag 为 true。
全局遮罩层
一个 fixed 定位宽高为100%的 div。
剩下的主要看 script 部分
<template>
<transition name="messagebox-fade" @after-leave="onDestroy">
<div @click="setVisiable(false)" v-show="visiable"
class="z-50 flex items-center justify-center fixed top-0 left-0 w-full h-full bg-dark-50/50">
<div @click.stop class="transform -translate-y-1/2 p-2 rounded min-w-3/7 bg-white">
<p class="text-sm text-gray-600 font-light"> {{ title }}</p>
<p class="my-4 text-lg">
<content-view :type="type"></content-view>
</p>
<div class="w-full flex justify-end items-center">
<button @click="okBtnClicked" class="btn btn-primary"> {{ ok }}</button>
<button @click="cancelBtnClicked" v-if="cancel" class="ml-2 btn btn-danger"> {{ cancel }}</button>
</div>
</div>
</div>
</transition>
</template>
<script setup>
import { ref, onMounted, h } from 'vue'
const { onOK, onCancel, message } = defineProps({
title: {
type: String,
default: '提示'
},
message: {
type: String,
default: ''
},
type: {
type: String,
validator: (value) => {
return ['confirm', 'prompt'].includes(value)
}
},
ok: {
type: String,
default: 'OK'
},
cancel: {
type: String,
default: ''
},
onDestroy: Function,
onOK: Function,
onCancel: Function
})
const promptContent = ref('')
const ContentView = ({ type }) => {
switch (type) {
case (!type || 'confirm'):
return h('p', null, message)
case 'prompt':
return h('input', {
class: 'messagebox-input',
onInput: (e) => promptContent.value = e.target.value
})
}
}
const visiable = ref(false);
const setVisiable = (vis) => {
visiable.value = vis;
}
const okBtnClicked = () => {
setVisiable(false);
onOK(promptContent.value)
}
const cancelBtnClicked = () => {
setVisiable(false)
onCancel()
}
onMounted(() => {
setVisiable(true);
})
</script>
<style scoped>
.btn {
@apply outline-gray-100 border-none p-1 rounded bg-warm-gray-200
}
.btn-primary {
@apply bg-sky-300
}
.messagebox-input {
@apply border rounded border-light-900 outline-none w-full py-1 px-2 text-lg
}
.messagebox-fade-enter-from,
.messagebox-fade-leave-to {
@apply opacity-0
}
.messagebox-fade-enter-active,
.messagebox-fade-leave-active {
@apply transition-opacity
}
</style>
函数式组件
// 第一个参数是 props,第二个参数是 context,类似 setup 的参数
// 返回值为 VNode
// 可以导出或者直接在组件内部使用
const ContentView = ({ type }) => {
switch (type) {
case (!type || 'confirm'):
return h('p', null, message)
case 'prompt':
return h('input', {
class: 'messagebox-input',
onInput: (e) => promptContent.value = e.target.value
})
}
}
封装 MessageBox 显示函数
import { createApp } from 'vue'
import MessageBoxCpn from './MessageBox.vue'
const fields = ['confirm', 'prompt']
export default function MessageBox(options) {
return new Promise((resolve, reject) => {
const app = createApp(MessageBoxCpn, {
...options,
onDestroy: () => {
app.unmount()
},
onOK: (value) => {
resolve(value)
},
onCancel: () => {
reject()
}
})
const frg = document.createDocumentFragment()
app.mount(frg)
document.body.appendChild(frg)
})
}
fields.forEach(field => {
MessageBox[field] = (options) => {
options.type = field
return MessageBox(options)
}
})
通过组件的 props 传入回调,实现按钮点击事件的传递、MessageBox 关闭时取消挂载的操作。
另外可以通过 MessageBox.prompt 等静态方法直接调用对应 type 的 MessageBox,实现方式是在 MessageBox 上挂上对应的静态方法,并且覆盖 options 的 type 选项。
更多推荐
所有评论(0)