本文整理了一个在 React 18 项目中最推荐的弹窗(Modal)实现方案,从基础写法到 Zustand 全局管理架构,包含:
- 可复用的基础 Modal 组件(无动画)
- 不同 UI 框架下的 Modal 写法(Tailwind / MUI / AntD / Radix)
- 专业项目使用的
useModalHook - 使用 Zustand 构建全局弹窗系统
- 架构图概念总结
1. 基础可复用 Modal 组件(核心逻辑)
React 18 推荐使用 Portal + 受控组件:
- Portal 避免受父组件 z-index / overflow 干扰
- open 状态来自外层(UI = state)
- Modal 负责展示,不负责逻辑
Modal.tsx
import React from "react";
import ReactDOM from "react-dom";
export function Modal({ open, onClose, children }) {
if (!open) return null;
return ReactDOM.createPortal(
<div className="fixed inset-0 bg-black/40 flex items-center justify-center" onClick={onClose}>
<div className="bg-white rounded-xl p-4" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}
2. 不同 UI 框架下的 Modal 写法
2.1 Tailwind 版(纯样式)
export function Modal({ open, onClose, children }) {
if (!open) return null;
return ReactDOM.createPortal(
<div className="fixed inset-0 bg-black/40 flex items-center justify-center" onClick={onClose}>
<div className="bg-white p-6 rounded-xl" onClick={(e) => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}
2.2 MUI 版
import Modal from "@mui/material/Modal";
import Box from "@mui/material/Box";
export function MuiModal({ open, onClose, children }) {
return (
<Modal open={open} onClose={onClose}>
<Box sx={{ bgcolor: "white", p: 3, borderRadius: 2, width: 300, mx: "auto", mt: "20vh" }}>
{children}
</Box>
</Modal>
);
}
2.3 AntD 版
import { Modal } from "antd";
export function AntdModal({ open, onClose, children }) {
return (
<Modal open={open} onCancel={onClose} footer={null}>
{children}
</Modal>
);
}
2.4 Radix UI 版(可访问性最佳)
import * as Dialog from "@radix-ui/react-dialog";
export function RadixModal({ open, onClose, children }) {
return (
<Dialog.Root open={open} onOpenChange={(v) => !v && onClose()}>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 bg-black/40" />
<Dialog.Content className="bg-white rounded-xl p-6 fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
{children}
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}
3. useModal —— 专业项目 Hook 写法
优点:
- 业务层代码更简洁
- 不需要外部管理 open 状态
- 组件渲染逻辑与状态控制解耦
useModal.ts
import { useState, useCallback } from "react";
export function useModal() {
const [open, setOpen] = useState(false);
const show = useCallback(() => setOpen(true), []);
const hide = useCallback(() => setOpen(false), []);
const ModalWrapper = useCallback(
({ children }) => (
<Modal open={open} onClose={hide}>
{children}
</Modal>
),
[open, hide]
);
return { open, show, hide, Modal: ModalWrapper };
}
4. Zustand 版本:企业级全局 Modal 管理器
用于大型项目——不再由各页面自行管理 open 状态,而是:
- 全局管理
- 多弹窗叠加(Modal Stack)
- 任意地方调用 showModal
- ModalRoot 统一渲染
4.1 Zustand Store
import { create } from "zustand";
let idCounter = 0;
export const useModalStore = create((set) => ({
modalStack: [], // { id, content }
showModal: (content) =>
set((state) => {
const id = ++idCounter;
return { modalStack: [...state.modalStack, { id, content }] };
}),
hideModal: (id) =>
set((state) => ({ modalStack: state.modalStack.filter((m) => m.id !== id) })),
hideTop: () =>
set((state) => ({ modalStack: state.modalStack.slice(0, -1) })),
}));
4.2 ModalRoot 全局渲染器
import ReactDOM from "react-dom";
import { useModalStore } from "../store/modalStore";
export function ModalRoot() {
const modalStack = useModalStore((s) => s.modalStack);
const hideModal = useModalStore((s) => s.hideModal);
return ReactDOM.createPortal(
<>
{modalStack.map((modal) => (
<div key={modal.id} className="fixed inset-0 bg-black/40 flex items-center justify-center" onClick={() => hideModal(modal.id)}>
<div className="bg-white p-4 rounded-xl" onClick={(e) => e.stopPropagation()}>
{modal.content}
</div>
</div>
))}
</>,
document.body
);
}
4.3 使用方式(任意组件)
import { useModalStore } from "./store/modalStore";
export default function Page() {
const showModal = useModalStore((s) => s.showModal);
const hideTop = useModalStore((s) => s.hideTop);
const open = () =>
showModal(
<>
<h2>确认操作?</h2>
<button onClick={hideTop}>关闭</button>
</>
);
return <button onClick={open}>打开弹窗</button>;
}
4.4 支持带参数的 confirm()
import { useModalStore } from "./store/modalStore";
export function confirm(message) {
return new Promise((resolve) => {
const { showModal, hideModal } = useModalStore.getState();
const id = showModal(
<div>
<p>{message}</p>
<button onClick={() => { hideModal(id); resolve(true); }}>确认</button>
<button onClick={() => { hideModal(id); resolve(false); }}>取消</button>
</div>
);
});
}
5. Zustand Modal 系统架构图(文字版)
业务组件 ---- showModal(content) ----> Zustand Store
|
V
modalStack: [{ id, content }]
|
ModalRoot ----------------------------------> Portal(render)
|
V
document.body
核心思想:
- 页面不管理弹窗状态
- Zustand 作为中央调度器
- ModalRoot 统一渲染
- 支持多弹窗叠加、参数传递、可扩展性极强
总结
这个 Modal 系统覆盖了从基础到企业级的所有常用方案:
- 基础可复用 Modal:Portal + 受控组件
- UI 框架版本:Tailwind / MUI / AntD / Radix
- useModal Hook:局部状态管理最优解
- Zustand 全局系统:大型项目统一弹窗路由