如何在Next.js中实现类似Sonner的DeleteModal组件?
在Next.js中实现全局可调用的DeleteModal组件
要实现类似Sonner的「全局渲染组件+函数调用触发」的效果,核心思路是通过全局状态管理+监听机制,让模态框的状态在组件间共享,同时提供简洁的调用函数。以下是具体实现步骤:
1. 创建全局状态管理模块
这个模块负责维护模态框的状态(显示/隐藏、提示文本、确认回调),并提供触发和关闭的函数,同时支持组件监听状态变化。
// app/delete-modal/store.js 'use client'; import React from 'react'; // 全局状态变量 let isOpen = false; let confirmCallback = () => {}; let message = ''; // 存储状态变化的监听函数 let listeners = []; // 通知所有监听者更新状态 const notifyListeners = () => { listeners.forEach(listener => listener({ isOpen, confirmCallback, message })); }; // 触发模态框显示的函数,外部组件直接调用 export const showDeleteModal = (options) => { isOpen = true; message = options.message || '确定要删除吗?'; confirmCallback = options.onConfirm || (() => {}); notifyListeners(); }; // 关闭模态框的函数 export const hideDeleteModal = () => { isOpen = false; notifyListeners(); }; // 自定义Hook,让组件和全局状态同步 export const useDeleteModal = () => { const [state, setState] = React.useState({ isOpen, confirmCallback, message }); React.useEffect(() => { // 注册监听函数 const listener = (newState) => setState(newState); listeners.push(listener); // 组件卸载时移除监听 return () => { listeners = listeners.filter(l => l !== listener); }; }, []); return { ...state, hideDeleteModal, }; };
2. 实现DeleteModal组件
这个组件通过自定义Hook获取全局状态,根据状态决定是否渲染,并处理确认/取消逻辑。
// app/components/DeleteModal.jsx 'use client'; import { useDeleteModal } from '../delete-modal/store'; export default function DeleteModal() { const { isOpen, message, confirmCallback, hideDeleteModal } = useDeleteModal(); // 状态为关闭时不渲染 if (!isOpen) return null; const handleConfirm = () => { // 执行传入的确认回调 confirmCallback(); // 关闭模态框 hideDeleteModal(); }; return ( <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> <div className="bg-white rounded-lg p-6 w-96 shadow-xl"> <h3 className="text-lg font-semibold mb-4">确认删除</h3> <p className="text-gray-700 mb-6">{message}</p> <div className="flex justify-end gap-3"> <button onClick={hideDeleteModal} className="px-4 py-2 border border-gray-300 rounded-md hover:bg-gray-50 transition-colors" > 取消 </button> <button onClick={handleConfirm} className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 transition-colors" > 删除 </button> </div> </div> </div> ); }
3. 在Layout中全局挂载模态框
在根布局中渲染一次DeleteModal,这样它就会在整个应用的生命周期中存在,无需在其他组件重复引入。
// app/layout.jsx import DeleteModal from './components/DeleteModal'; export default function RootLayout({ children }) { return ( <html lang="zh-CN"> <body className="min-h-screen"> {children} {/* 全局模态框,只需要渲染一次 */} <DeleteModal /> </body> </html> ); }
4. 在任意组件中调用触发
在需要触发删除确认的组件中,直接导入showDeleteModal函数调用即可,无需再处理模态框的渲染逻辑。
// app/components/ItemCard.jsx 'use client'; import { showDeleteModal } from '../delete-modal/store'; export default function ItemCard({ itemId, itemName }) { const handleDeleteItem = () => { showDeleteModal({ message: `确定要删除「${itemName}」吗?此操作不可恢复。`, onConfirm: async () => { // 这里编写实际的删除逻辑,比如调用API console.log(`删除ID为${itemId}的项`); // 可以在这里添加删除后的提示或页面更新逻辑 }, }); }; return ( <div className="border p-4 rounded-lg flex justify-between items-center"> <span>{itemName}</span> <button onClick={handleDeleteItem} className="text-red-600 hover:text-red-800 font-medium" > 删除 </button> </div> ); }
关键说明
- 客户端组件标记:所有涉及状态交互的组件(DeleteModal、调用showDeleteModal的组件)都需要添加
'use client'指令,因为Next.js App Router默认是服务器组件,无法处理客户端状态。 - 状态同步机制:通过监听函数数组,让全局状态的变化能同步到DeleteModal组件,确保模态框能正确响应显示/隐藏操作。
- 灵活性:可以根据需求扩展
showDeleteModal的参数,比如添加取消回调、自定义按钮文本等。
内容的提问来源于stack exchange,提问作者drg_iulian
相关产品推荐
相关产品推荐

