React中如何使用react-router的usePrompt实现路由切换提示?
React Router 路由切换提示功能失效问题解决
我正在基于react-router开发提示功能,希望在切换路由(页面)时触发提示弹窗。目前已配置相关属性但功能未生效,请问有什么解决建议?如何实现所有路由切换时均显示提示信息?
你的代码
import { BrowserRouter as Router, Routes, Route, useHistory, usePrompt, useBlocker, Navigate } from "react-router-dom"; import Demo from "./Demo"; import About from "./About"; import React, { useEffect, useState } from "react"; import "./styles.css"; export default function App() { const [login, setLogin] = React.useState(false); return ( <div className="App"> <Demo /> <button onClick={() => setLogin(!login)}> {login ? "LogOut" : "LogIn"} </button> <Routes> <Route exact path="/" element={<Home />} onLeave={ BlockingForm } /> {/*Pass Props to a Component Rendered by React Router */} <Route path="/about" element={<About login={login} />} onLeave={ BlockingForm }/> {/* if you want to navigate home page if no page found */} <Route path="*" element={true ? <NotFound /> : <Navigate replace="/" />} /> </Routes> </div> ); } function Home() { return <div>Home component</div>; } function NotFound() { return <div>404 Not Found Page</div>; } function BlockingForm() { let [isBlocking, setIsBlocking] = useState(false); usePrompt( "Hello from usePrompt -- Are you sure you want to leave?", isBlocking ); // useBlocker( // () => "Hello from useBlocker -- are you sure you want to leave?", // isBlocking // ); return ( <form onSubmit={(event) => { event.preventDefault(); event.target.reset(); setIsBlocking(false); }} > <p> Blocking? {isBlocking ? "Yes, click a link or the back button" : "Nope"} </p> <p> <input size="50" placeholder="type something to block transitions" onChange={(event) => { setIsBlocking(event.target.value.length > 0); }} /> </p> <p> <button>Submit to stop blocking</button> </p> </form> ); }
核心问题分析
- React Router v6 已移除
onLeave属性:你代码里在<Route>上使用的onLeave是v5的API,v6完全删除了这个属性,所以配置的onLeave={BlockingForm}根本不会生效。 usePrompt/useBlocker使用位置错误:这两个Hook必须在被<Router>包裹的组件内部使用,且需要直接作用于路由切换的拦截逻辑,而不是作为onLeave的参数传入。- 原代码缺少
<Router>包裹:整个路由系统必须被<Router>组件包裹,否则usePrompt这类依赖路由上下文的Hook无法工作。 - 单路由配置无法实现全局拦截:你当前的写法是给单个路由配置拦截,要实现所有路由切换都提示,需要全局层面的拦截逻辑。
解决方法
方法1:实现全局路由提示(所有切换都触发)
创建一个全局拦截组件,放在<Routes>外层,确保所有路由切换都会触发提示:
import { BrowserRouter as Router, Routes, Route, usePrompt, Navigate } from "react-router-dom"; import Demo from "./Demo"; import About from "./About"; import React, { useState } from "react"; import "./styles.css"; // 全局路由拦截组件 function GlobalRouteGuard() { // 设为true则所有路由切换都触发提示,可通过状态动态控制拦截条件 const [isBlocking, setIsBlocking] = useState(true); usePrompt("确定要离开当前页面吗?", isBlocking); return null; // 该组件无需渲染可视内容 } export default function App() { const [login, setLogin] = React.useState(false); return ( <Router> {/* 必须用Router包裹整个路由系统 */} <div className="App"> <Demo /> <button onClick={() => setLogin(!login)}> {login ? "LogOut" : "LogIn"} </button> <GlobalRouteGuard /> {/* 全局拦截组件放在Routes外层 */} <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About login={login} />} /> <Route path="*" element={<NotFound />} /> </Routes> </div> </Router> ); } function Home() { return <div>Home component</div>; } function NotFound() { return <div>404 Not Found Page</div>; }
方法2:针对特定页面实现拦截(比如表单页面)
如果只需要在特定页面(如未保存的表单页面)触发提示,直接在该页面组件内部使用usePrompt:
function BlockingForm() { const [isBlocking, setIsBlocking] = useState(false); // 输入框有内容时拦截路由切换 usePrompt("确定要离开吗?输入的内容会丢失", isBlocking); return ( <form onSubmit={(e) => { e.preventDefault(); setIsBlocking(false); alert("表单提交成功"); }} > <p>输入内容后切换路由会触发提示</p> <input placeholder="输入内容..." onChange={(e) => setIsBlocking(e.target.value.length > 0)} /> <button type="submit">提交</button> </form> ); } // 在路由中挂载该组件 <Route path="/form" element={<BlockingForm />} />
关键注意事项
- 务必用
<Router>包裹:所有使用路由Hook的组件都必须在<Router>的上下文范围内,否则Hook会失效。 - 动态控制拦截条件:通过
isBlocking状态变量,可以灵活控制什么时候触发提示,比如仅当表单有未保存内容时拦截。 - 区分
usePrompt和useBlocker:usePrompt是简单的确认弹窗,useBlocker支持自定义拦截逻辑(比如异步请求确认),复杂场景可选用useBlocker。
内容的提问来源于stack exchange,提问作者Sougata Mukherjee
相关产品推荐
相关产品推荐

