useRouteLeaveConfirm 路由离开确认弹窗 Hook
组件说明:
useRouteLeaveConfirm
是一个自定义 React Hook,用于在页面有未保存内容时,阻止路由切换并弹出确认弹窗。支持自定义弹窗内容和按钮,适用于表单编辑等场景。
代码实现:
## 代码实现import { useEffect, useRef } from 'react';
import { useBlocker } from 'react-router-dom';
import { Modal, Button } from 'antd';export function useRouteLeaveConfirm({when,message = '当前页面有未保存的内容,确定要离开吗?',title = '离开确认',onOk,onCancel,confirmConfig = {},
}) {const modalRef = useRef(null);const blocker = useBlocker(() => when);useEffect(() => {if (blocker && blocker.state === 'blocked') {if (modalRef.current) {modalRef.current.destroy();modalRef.current = null;}const handleClose = () => {if (modalRef.current) {modalRef.current.destroy();modalRef.current = null;}};// 从 confirmConfig 中解构出 buttons,其余的 props 依然传递给 Modalconst { buttons, ...restConfig } = confirmConfig;const modalProps = {title,content: message,keyboard: false,...restConfig,};// 如果传入了自定义 buttons,则使用 footer 来自定义按钮if (buttons && buttons.length > 0) {modalProps.footer = (<div style={{ textAlign: 'right' }}>{buttons.map((button, index) => (<ButtonclassName='ml-2'key={index}{...button.props}onClick={async () => {let shouldClose = true;if (button.onClick) {// 允许 onClick 返回 false 来阻止弹窗关闭const result = await Promise.resolve(button.onClick());if (result === false) {shouldClose = false;}}if (button.action === 'proceed') {blocker.proceed?.();} else if (button.action === 'reset') {blocker.reset?.();}if (shouldClose) {handleClose();}}}>{button.text}</Button>))}</div>);} else {// 否则,使用默认的 onOk 和 onCancelmodalProps.okText = confirmConfig.okText || '确定';modalProps.cancelText = confirmConfig.cancelText || '取消';modalProps.onOk = async () => {try {await onOk?.();} finally {blocker.proceed?.();handleClose();}};modalProps.onCancel = () => {onCancel?.();blocker.reset?.();handleClose();};}modalRef.current = Modal.confirm(modalProps);}if (!when && modalRef.current) {modalRef.current.destroy();modalRef.current = null;}}, [blocker, when, title, message, onOk, onCancel, confirmConfig]);useEffect(() => {return () => {if (modalRef.current) {modalRef.current.destroy();}};}, []);
}
```## API| 参数 | 类型 | 说明 |
| ------------ | --------- | -------------------------------------- |
| when | boolean | 是否阻止路由切换并弹窗 |
| title | string | 弹窗标题 |
| message | string | 弹窗内容 |
| onOk | function | 点击“确定”时回调 |
| onCancel | function | 点击“取消”时回调 |
| confirmConfig| object | 弹窗配置,支持自定义按钮、内容等 |### confirmConfig.buttons
- `text`:按钮文本
- `action`:'proceed'(继续切换)、'reset'(取消切换)
- `props`:Antd Button 额外属性
- `onClick`:按钮点击回调,返回 `false` 可阻止弹窗关闭
用法示例:
###使用示例```jsx
import { useRouteLeaveConfirm } from '@/hooks/useRouteLeaveConfirm';const confirmConfig = useMemo(() => ({buttons: [{ text: '取消', action: 'reset' },{ text: '不保存', action: 'proceed' },{ text: '保存', props: { type: 'primary' },action: 'proceed', onClick: handleSave },],
}), [handleSave]);useRouteLeaveConfirm({when: isDirty,title: '您有未保存的更改',message: '您想在离开前保存吗?',confirmConfig,
});
```isDirty 这个值用于标记当前页面内容是否被修改但尚未保存。 true 弹框弹出 如果前后数据一致,没有发生什么变化则为false
action: 'proceed' //这个是用来控制执行完事件后是否要跳转页面## 注意事项
- 当我们执行的保存逻辑是异步函数时,记得加上async/awiat
- 依赖 react-router-dom v6 的 `useBlocker`。
- 弹窗会在路由切换被阻止时自动弹出,无需手动调用。
- 支持自定义按钮和回调,满足复杂交互需求。