组件封装实战:手写一个可复用的Modal弹窗组件(Vue/React)
·
核心设计思路
基于Vue和React的Modal组件需要遵循以下设计原则:
- 状态驱动:通过
visible属性控制显示/隐藏 - 插槽机制:支持内容区域、头部、底部插槽自定义
- 事件系统:提供
onClose、onConfirm等回调 - 无障碍访问:实现焦点管理、键盘事件监听
Vue 3实现方案
组件模板结构
<template>
<transition name="fade">
<div
v-show="visible"
class="modal-mask"
@click.self="handleMaskClick"
>
<div class="modal-container">
<header v-if="$slots.header">
<slot name="header"></slot>
</header>
<div class="modal-body">
<slot></slot>
</div>
<footer v-if="$slots.footer || showDefaultFooter">
<slot name="footer">
<button @click="handleCancel">取消</button>
<button @click="handleConfirm">确认</button>
</slot>
</footer>
</div>
</div>
</transition>
</template>
组件逻辑实现
import { watchEffect } from 'vue'
export default {
props: {
visible: Boolean,
closeOnClickMask: {
type: Boolean,
default: true
}
},
emits: ['update:visible', 'close', 'confirm'],
setup(props, { emit }) {
const handleMaskClick = () => {
if (props.closeOnClickMask) {
emit('update:visible', false)
emit('close')
}
}
const handleCancel = () => {
emit('update:visible', false)
emit('close')
}
const handleConfirm = () => {
emit('confirm')
}
watchEffect(() => {
document.body.style.overflow = props.visible ? 'hidden' : ''
})
return { handleMaskClick, handleCancel, handleConfirm }
}
}
React实现方案
函数组件实现
import { useEffect } from 'react'
export default function Modal({
visible,
onClose,
onConfirm,
closeOnClickMask = true,
children,
footer
}) {
const handleMaskClick = () => {
if (closeOnClickMask) {
onClose?.()
}
}
useEffect(() => {
document.body.style.overflow = visible ? 'hidden' : ''
return () => {
document.body.style.overflow = ''
}
}, [visible])
if (!visible) return null
return (
<div className="modal-mask" onClick={handleMaskClick}>
<div className="modal-container" onClick={e => e.stopPropagation()}>
<div className="modal-body">{children}</div>
{footer ?? (
<div className="modal-footer">
<button onClick={onClose}>取消</button>
<button onClick={onConfirm}>确认</button>
</div>
)}
</div>
</div>
)
}
样式关键点
基础样式框架
.modal-mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-container {
background: white;
border-radius: 4px;
min-width: 300px;
max-width: 80%;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
高级功能扩展
动态挂载方案
// Vue示例:通过createApp动态挂载
export function showModal(options) {
const container = document.createElement('div')
const app = createApp(Modal, {
...options,
onClose: () => {
options.onClose?.()
unmountModal()
}
})
const unmountModal = () => {
app.unmount()
document.body.removeChild(container)
}
document.body.appendChild(container)
app.mount(container)
}
键盘事件处理
// React示例:ESC键关闭
useEffect(() => {
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
onClose?.()
}
}
if (visible) {
document.addEventListener('keydown', handleKeyDown)
}
return () => {
document.removeEventListener('keydown', handleKeyDown)
}
}, [visible, onClose])
性能优化建议
- 使用CSS
will-change属性优化动画性能 - 对于频繁开关的Modal,采用
keep-alive缓存组件状态(Vue) - 避免在Modal内部使用大量响应式数据
- 按需加载Modal内容(如配合Suspense使用)
更多推荐


所有评论(0)