Vue3 实现 Modal 组件
使用 Vue3 设计和实现一个可复用的 Modal 弹窗组件
问题
如何使用 Vue3 实现一个功能完整的 Modal 组件,支持模板调用和 API 调用两种方式?
解答
设计思路
Modal 组件需要实现以下功能:
- 遮罩层、标题、主体内容、确定和取消按钮
- 主体内容支持字符串、插槽、h 函数和 JSX
- 支持模板方式和 API 方式调用
- 使用 Teleport 将组件挂载到 body
目录结构
plugins/modal
├── Modal.vue # 基础组件
├── Content.tsx # 内容渲染组件
├── index.ts # 入口文件
├── config.ts # 全局配置
├── modal.type.ts # TypeScript 类型
└── locale # 国际化
└── lang
组件实现
Modal.vue 核心代码:
<template>
<Teleport to="body" :disabled="!isTeleport">
<div v-if="modelValue" class="modal">
<div class="mask"></div>
<div class="modal__wrapper">
<div class="modal__header">{{ title }}</div>
<div class="modal__content">
<Content v-if="typeof content === 'function'" :render="content" />
<slot v-else>{{ content }}</slot>
</div>
<div class="modal__footer">
<button @click="handleCancel">取消</button>
<button @click="handleConfirm">确定</button>
</div>
</div>
</div>
</Teleport>
</template>
<script setup>
import { getCurrentInstance, onBeforeMount } from 'vue';
import Content from './Content';
const props = defineProps({
modelValue: Boolean,
title: String,
content: [String, Function]
});
const emit = defineEmits(['update:modelValue', 'confirm', 'cancel']);
const instance = getCurrentInstance();
onBeforeMount(() => {
instance._hub = {
'on-cancel': () => {},
'on-confirm': () => {}
};
});
const handleConfirm = () => {
instance._hub['on-confirm']();
emit('confirm');
emit('update:modelValue', false);
};
const handleCancel = () => {
instance._hub['on-cancel']();
emit('cancel');
emit('update:modelValue', false);
};
</script>
模板调用方式
<!-- 使用插槽 -->
<Modal v-model="show" title="演示 slot">
<div>hello world~</div>
</Modal>
<!-- 使用字符串 -->
<Modal v-model="show" title="演示 content" content="hello world~" />
API 调用方式
index.ts 实现:
import { createVNode, render } from 'vue';
import Modal from './Modal.vue';
const create = (options) => {
const container = document.createElement('div');
const vnode = createVNode(Modal, {
modelValue: true,
...options
});
render(vnode, container);
document.body.appendChild(container);
const instance = vnode.component;
const { props, _hub } = instance;
const _closeModal = () => {
props.modelValue = false;
container.parentNode.removeChild(container);
};
Object.assign(_hub, {
'on-confirm': async () => {
if (options.onConfirm) {
await options.onConfirm();
}
_closeModal();
},
'on-cancel': () => {
if (options.onCancel) {
options.onCancel();
}
_closeModal();
}
});
return instance;
};
export default {
install(app) {
app.config.globalProperties.$modal = {
show: create
};
}
};
使用 h 函数:
$modal.show({
title: '演示 h 函数',
content(h) {
return h(
'div',
{
style: 'color:red;',
onClick: ($event) => console.log('clicked', $event.target)
},
'hello world~'
);
},
onConfirm: () => console.log('确认'),
onCancel: () => console.log('取消')
});
使用 JSX:
$modal.show({
title: '演示 JSX',
content() {
return (
<div onClick={($event) => console.log('clicked', $event.target)}>
hello world~
</div>
);
}
});
关键点
- 使用 Teleport 将 Modal 挂载到 body,避免样式层级问题
- 通过 createVNode 和 render 实现 API 调用方式,替代 Vue2 的 Vue.extend
- 内容支持多种形式:字符串、插槽、h 函数和 JSX,提高组件灵活性
- 使用 _hub 对象管理事件回调,统一处理模板调用和 API 调用的事件
- 通过 app.config.globalProperties 挂载全局方法,适配 Vue3 的 Composition API
目录