组件说明:
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 依然传递给 Modal
const { 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) => (
<Button
className='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 和 onCancel
modalProps.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`。
- 弹窗会在路由切换被阻止时自动弹出,无需手动调用。
- 支持自定义按钮和回调,满足复杂交互需求。

被折叠的 条评论
为什么被折叠?



