react-router 6 中实现 Prompt ,阻止页面切换与关闭

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 相关事宜时,也是一个不错的解决方法。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
在 `react-router-dom` v6 ,路由拦截的方式与 v5 有所不同。下面是一个简单的示例: ```javascript import React, { useState } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, Navigate } from 'react-router-dom'; const isAuthenticated = true; const PrivateRoute = ({ path, element }) => { return isAuthenticated ? ( element ) : ( <Navigate to='/login' replace /> ); }; const LoginPage = () => ( <div> <h1>Login Page</h1> </div> ); const HomePage = () => ( <div> <h1>Home Page</h1> </div> ); const App = () => { return ( <Router> <div> <ul> <li><Link to='/'>Home</Link></li> <li><Link to='/login'>Login</Link></li> </ul> <hr /> <Routes> <PrivateRoute path='/' element={<HomePage />} /> <Route path='/login' element={<LoginPage />} /> </Routes> </div> </Router> ); }; export default App; ``` 在上面的代码,我们定义了一个 `PrivateRoute` 组件来实现路由拦截。如果用户已经认证,则展示 `element` 组件,否则重定向到登录页面。 另外,我们还定义了两个页面组件:`HomePage` 和 `LoginPage`。在 `Routes` 组件,我们使用 `PrivateRoute` 组件来保护 `HomePage` 组件,这样只有已经认证的用户才能访问该页面。 如果你想在路由跳转之前进行相关的操作,可以使用 `onBeforeChange` 函数。下面是一个简单的示例: ```javascript import React, { useState } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, Prompt } from 'react-router-dom'; const App = () => { const [isBlocking, setIsBlocking] = useState(false); const handleChange = (event) => { setIsBlocking(event.target.value.length > 0); }; const handleBeforeChange = (nextLocation) => { if (isBlocking) { return window.confirm(`Are you sure you want to go to ${nextLocation.pathname}?`); } return true; }; return ( <Router> <div> <ul> <li><Link to='/'>Home</Link></li> <li><Link to='/about'>About</Link></li> </ul> <hr /> <Prompt when={isBlocking} message={location => `Are you sure you want to go to ${location.pathname}?`} /> <Routes onBeforeChange={handleBeforeChange}> <Route path='/' element={<HomePage />} /> <Route path='/about' element={<AboutPage />} /> </Routes> </div> </Router> ); }; export default App; ``` 在上面的代码,我们使用 `onBeforeChange` 函数来拦截路由,在用户输入内容时,如果尚未保存,就弹出提示框。如果用户选择离开当前页面,则路由会跳转到目标页面

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值