在
react-router6
中将prompt
及其相关的处理方法去除掉了,但是在一些时候又必须使用。所以,有必要了解一下如何解决这个问题。
仅限于浏览器环境。
git地址: https://gitee.com/wkjgit/prompt.git
npm地址: https://www.npmjs.com/package/react-router6-prompt
补充: 已添加HashRouter
支持,并发布到npm
上。
由于 react-router6
是以这样的形式使用:
import {BrowserRouter} from 'react-router-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
但是 react-router
或者 react-router-dom
都没有导出 history
模块,所以,我们需要重新定义一下 BrowserRouter
组件,用来将 history
导出,并在后面使用此处的 history
来实现具体的页面转换拦截。
自定义 BrowserRouter
导出 history
import React from 'react';
import { createBrowserHistory } from "history";
import type { BrowserHistory } from "history";
import { Router } from "react-router";
export interface BrowserRouterProps {
basename?: string;
children?: React.ReactNode;
window?: Window;
}
const browserHistory = createBrowserHistory({ window });
const BrowserRouter: React.FC = ({
basename,
children,
window,
}: BrowserRouterProps) => {
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = browserHistory;
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location,
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
);
}
export {
browserHistory as history,
BrowserRouter
}
这个组件在原有的基础上只是简单的将 history
导出了,仅此而已。
创建 Prompt
组件,用来实现具体的页面切换/卸载限制
import React, { useEffect } from 'react';
import {history} from './BrowserRouter';
import {Blocker, Location} from 'history';
import {useLocation, useNavigate} from 'react-router-dom';
type Props = {
when: boolean;
message?: string | (() => boolean) | (() => Promise<boolean>);
}
const Prompt: React.FC<Props> = (props) => {
const location = useLocation();
const navigate = useNavigate();
// 存储关闭阻止页面切换的方法(调用此方法将关闭阻止页面切换)
let unblock: any = null;
// 阻止页面卸载
const beforeUnload = (event: any) => {
event.preventDefault();
event.returnValue = '';
}
// 页面切换时的回调
const handlePageChange: Blocker = async ({location: nextLocation}) => {
// 是否关闭切换限制并跳转
let toNext: boolean = false;
if (props.message) {
if (typeof props.message === "string") {
toNext = confirm(props.message);
} else {
toNext = await props.message();
}
} else {
toNext = confirm("是否放弃更改");
}
toNext && closeBlockAndNavigate(nextLocation);
}
// 关闭阻止页面切换
const closeBlockPageSwitching = () => {
if (unblock) {
unblock();
unblock = null;
window.removeEventListener("beforeunload", beforeUnload);
}
}
// 关闭阻止页面切换,并跳转
const closeBlockAndNavigate = (nextLocation: Location) => {
closeBlockPageSwitching();
navigate(nextLocation);
}
// 监听when 和 pathname 变化,当发生变化时判断是否需要开启block navigate.
useEffect(() => {
if (props.when) {
// 阻塞页面跳转(history行为)
unblock = history.block(handlePageChange);
window.addEventListener('beforeunload', beforeUnload);
}
return () => {
props.when && closeBlockPageSwitching();
}
}, [props.when, location.pathname]);
return (
<></>
);
}
export default Prompt;
使用
import { useState } from 'react'
import logo from './logo.svg'
import './App.css'
import Prompt from './components/Prompt'
import {NavLink, useRoutes, RouteObject} from 'react-router-dom';
const Home = () => {
return (
<div>home</div>
);
}
const About = () => {
return (
<div>about</div>
);
}
const routes: RouteObject[] = [
{
path: '/',
element: <Home />
},
{
path: '/about',
element: <About />
}
];
function App() {
const [count, setCount] = useState<boolean>(false);
const router = useRoutes(routes);
return (
<div className="App">
<NavLink to="/">HOME</NavLink>
<NavLink to="/about">ABOUT</NavLink>
<button onClick={() => { setCount(!count) }}>测试</button>
<Prompt when={count} message={() => {
alert();
return true;
}} />
{router}
</div>
)
}
export default App;
这里只是提供了一个实现思路,当然直接使用也是没问题的。
在目前react-router6
还未支持Prompt
相关事宜时,也是一个不错的解决方法。