
一句话:用
useNavigate()在「事件处理、异步回调、Effect」里跳转——它支持replace(替换历史)、state(传状态)与相对路径(../.);渲染时的重定向用<Navigate />。
一、基础用法
import { useNavigate } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const handleLogin = async () => {
await login();
navigate('/dashboard'); // ⭐ 跳转到 /dashboard
};
return <button onClick={handleLogin}>登录</button>;
}
// 三种常用跳转形式
const navigate = useNavigate();
navigate('/about'); // ① 绝对路径
navigate('settings'); // ② 相对路径(当前路由下的 settings)
navigate(-1); // ③ 数字(历史记录里的偏移)
navigate(-1); // 后退一步
navigate(1); // 前进一步
| 参数 | 含义 |
|---|---|
'/about' |
绝对路径 |
'settings' |
相对路径(相对于当前路由) |
-1 / 1 |
历史记录偏移(后退 / 前进,等价 history.go(-1)) |
二、replace 与 state
// ① replace:替换当前历史记录(不会留下「后退」的一步)
navigate('/login', { replace: true });
// 典型场景:登录成功后跳转到首页
// ❌ 不加 replace → 用户点「后退」会回到登录页(体验不好)
navigate('/dashboard'); // ⚠️ 后退会回到 /login
navigate('/dashboard', { replace: true }); // ✅ 后退不会回到登录页
// ② state:传递「不进 URL」的数据
navigate('/checkout', { state: { from: 'cart', items } });
// 接收方用 useLocation 读取
function Checkout() {
const location = useLocation();
const { from, items } = (location.state as { from?: string; items?: OrderItem[] }) ?? {};
// ⚠️ state 不在 URL 里 → 刷新后仍然保留(存在 history 条目里),
// 但「直接打开链接」时没有 state → 需要兜底处理
return <div>来自 {from},共 {items?.length ?? 0} 件</div>;
}
state 的适用场景:
✅ 传递「临时/敏感/无需分享」的数据(如表单草稿、来源页面、列表滚动位置)
❌ 不适合传递「需要分享/刷新恢复」的数据(应该放 URL)
⚠️ 刷新页面时 state 仍在(因为它在 history 条目里),
但如果用户「复制链接分享」或「直接打开」,state 就是 undefined → 必须有兜底
三、相对路径
// 假设当前路径是 /users/123/orders
const navigate = useNavigate();
navigate('detail'); // → /users/123/orders/detail(子路径)
navigate('..'); // → /users/123(上一级)
navigate('../..'); // → /users
navigate('.'); // → /users/123/orders(当前路径)
navigate('/about'); // → /about(绝对路径)
// ⚠️ 相对路径在「嵌套路由」里很有用(不用拼绝对路径)
function OrderActions() {
const navigate = useNavigate();
const goToDetail = () => navigate('detail'); // ⭐ 不用写 /users/123/orders/detail
const goBackToList = () => navigate('..');
return (
<>
<button onClick={goToDetail}>查看详情</button>
<button onClick={goBackToList}>返回列表</button>
</>
);
}
// ✅ <Link> 也支持相对路径
<Link to="detail">详情</Link>
<Link to="..">返回</Link>
<Link to=".." relative="path">返回</Link>
// relative="route"(默认):相对「当前路由」
// relative="path":相对「当前路径」
// ⚠️ 两者在「匹配了通配符/多层路由」时有差异,通常默认即可
| 场景 | 用哪个 |
|---|---|
| 事件处理里跳转(点击、提交后) | useNavigate() |
| 异步回调里跳转(请求成功/失败后) | useNavigate() |
| 渲染时的条件重定向(未登录) | <Navigate /> |
| 路由配置里的静态重定向 | <Navigate /> |
// ✅ 渲染时重定向(声明式)
function RequireAuth({ children }: { children: React.ReactNode }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return <>{children}</>;
}
// ⚠️ 不要在渲染中调用 navigate(会有副作用 + 可能循环)
function Bad() {
const navigate = useNavigate();
const { user } = useAuth();
if (!user) {
navigate('/login'); // ❌ 渲染期间调用 navigate
}
return <div>...</div>;
}
// ✅ 用 <Navigate />(声明式)
function Good() {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
return <div>...</div>;
}
// ✅ 或放到 Effect 里(如果必须用命令式)
useEffect(() => {
if (!user) navigate('/login', { replace: true });
}, [user, navigate]);
五、常见场景
// ① 登录后跳回「来源页面」
function Login() {
const navigate = useNavigate();
const location = useLocation();
const from = (location.state as { from?: Location })?.from?.pathname ?? '/dashboard';
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await login();
navigate(from, { replace: true }); // ⭐ 回到来源,且不留登录页历史
};
return <form onSubmit={handleSubmit}>...</form>;
}
// 配合 RequireAuth 的写法
function RequireAuth({ children }: { children: React.ReactNode }) {
const { user } = useAuth();
const location = useLocation();
if (!user) return <Navigate to="/login" state={{ from: location }} replace />;
return <>{children}</>;
}
// ② 提交成功后跳转(带提示)
const handleSubmit = async () => {
await save();
toast.success('保存成功');
navigate('/list', { replace: true });
};
// ③ 删除后返回列表(或直接后退)
const handleDelete = async (id: string) => {
await remove(id);
navigate(-1); // ⭐ 后退一步(回到列表)
// 或 navigate('/list', { replace: true });
};
// ④ 保持查询参数地跳转
const [searchParams] = useSearchParams();
navigate(`/detail/${id}?${searchParams.toString()}`);
// ⑤ 在「非组件」的地方跳转(如 axios 拦截器)
// ❌ Hooks 只能在组件/Hook 里用 → 需要把 navigate 传出去或建立「导航服务」
// ✅ 方案一:在根组件注册
let globalNavigate: ReturnType<typeof useNavigate> | null = null;
function NavigationBridge() {
globalNavigate = useNavigate();
return null;
}
// 拦截器里
if (res.status === 401) {
globalNavigate?.('/login', { replace: true });
}
// ✅ 方案二:用事件 + 组件监听
window.dispatchEvent(new CustomEvent('auth:expired'));
六、导航阻塞(useBlocker)
// ⚠️ 注意:useBlocker 只在【数据路由】(createBrowserRouter)下可用
import { useBlocker, Form } from 'react-router-dom';
function EditForm() {
const [dirty, setDirty] = useState(false);
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
dirty && currentLocation.pathname !== nextLocation.pathname
);
return (
<>
<Form>{/* ... */}</Form>
{blocker.state === 'blocked' && (
<Modal>
<p>有未保存的修改,确定离开吗?</p>
<button onClick={() => blocker.proceed()}>离开</button>
<button onClick={() => blocker.reset()}>留下</button>
</Modal>
)}
</>
);
}
// 只阻止「站内导航」;浏览器前进/后退需要额外处理
useEffect(() => {
const handler = (e: BeforeUnloadEvent) => {
if (!dirty) return;
e.preventDefault(); // ⭐ 触发浏览器原生确认
};
window.addEventListener('beforeunload', handler);
return () => window.removeEventListener('beforeunload', handler);
}, [dirty]);
七、常见坑
// ① 渲染期间调用 navigate
function Bad() {
navigate('/x'); // ❌ 副作用发生在渲染阶段
}
// ② 登录成功没加 replace → 后退回到登录页
navigate('/dashboard'); // ⚠️
navigate('/dashboard', { replace: true }); // ✅
// ③ 用 state 传递「需要分享」的数据
navigate('/detail', { state: { id } }); // ⚠️ 刷新/分享后丢失
navigate(`/detail/${id}`); // ✅ 放 URL
// ④ 相对路径用错(以为总是相对根路径)
navigate('settings'); // ⚠️ 相对当前路由,不是根
navigate('/settings'); // ✅ 绝对路径
// ⑤ navigate(-1) 在「没有历史」时无效(用户直接打开链接)
navigate(-1); // ⚠️ 可能什么都不发生
// ✅ 兜底:navigate(-1) 但检查 location.key === 'default' 时改为 navigate('/')
// ⑥ 在事件处理里「先跳转再 await」
const handle = async () => {
navigate('/next'); // ⚠️ 组件卸载后仍 await 后续逻辑
await save(); // → 可能在已卸载组件里 setState
};
// ✅ 先 await 再 navigate
// ⑦ 组件卸载后调用 navigate(React 18 不会警告,但逻辑可能不对)
// ⑧ 用 <a href> 代替 navigate(整页刷新)
// ⑨ 在 useEffect 里 navigate 但依赖写错 → 只在挂载时跳一次(可能符合预期,也可能不是)
// ⑩ 「路由跳转」与「状态更新」混在一起导致「更新被丢弃」
// ⑪ 在 axios 拦截器里直接 import useNavigate(Hooks 不能在组件外调用)
// ⑫ 忘记「navigate 会让组件卸载」→ 之后的 setState 无意义
// ⑤ 的兜底写法
function useSafeBack() {
const navigate = useNavigate();
const location = useLocation();
return useCallback(() => {
// location.key === 'default' 表示「这是会话中的第一个条目」(没有可后退的历史)
if (location.key === 'default') {
navigate('/', { replace: true });
} else {
navigate(-1);
}
}, [navigate, location.key]);
}
八、面试延伸
- 「怎么做编程式导航?」
用useNavigate()拿到navigate函数,然后在「事件处理、异步回调、Effect」里调用它:navigate('/path')跳转、navigate(-1)后退、navigate('/path', { replace: true })替换历史、navigate('/path', { state })传状态。渲染时的条件重定向不要用navigate(那是副作用),应该用<Navigate to="..." replace />组件。 -
「
replace有什么用?」
replace: true表示「替换当前的历史记录」而不是「压入新的一页」。典型场景是登录/注册成功后跳转——如果不加replace,用户点「后退」会回到登录页(体验很差,而且可能触发「已登录还显示登录页」的怪异状态)。同理「表单提交成功后的跳转」「重定向」也应该用replace。 -
「
navigate的state和查询参数有什么区别?」
①state存在「history 条目」里,不进入 URL——适合传「临时数据、来源页面、敏感信息」;优点是无需序列化、刷新后仍在;缺点是「不能分享」「直接打开链接时是undefined」(所以必须有兜底)。② 查询参数(?a=1) 是 URL 的一部分——适合传「可分享、可刷新恢复」的状态(如筛选条件、页码)。判断标准是「这个状态是否需要被分享/收藏」。 -
「相对路径导航怎么用?」
相对路径是「相对于当前路由」的:navigate('detail')会跳到「当前路径的子路径」,navigate('..')跳到「上一级」,navigate('../..')上两级。这在嵌套路由里很有价值——比如在/users/123/orders里想跳到/users/123/orders/detail,只需navigate('detail'),不用拼接绝对路径。注意<Link>还有relative="route"(默认,相对路由)/"path"(相对路径)的区分。 -
「为什么不能在渲染中调用
navigate?」
因为「跳转」是一个副作用(它会改变 URL、触发路由变化与组件卸载/挂载)。而渲染阶段(render phase)必须是纯的——React 可能重复执行它、也可能丢弃它。在渲染中调用navigate会导致「不可预期的多次跳转」甚至「无限循环」(跳转 → 重新渲染 → 又跳转)。渲染时的重定向用<Navigate />,命令式跳转放到事件处理或useEffect里。 -
「怎么实现『登录后跳回原页面』?」
两步:① 在「路由守卫」里把「当前位置」放进state:<Navigate to="/login" state={{ from: location }} replace />;② 登录成功后从location.state.from取出原路径并跳过去(同时用replace: true避免留下登录页历史):navigate(from?.pathname ?? '/dashboard', { replace: true })。这是一个非常常见的实战模式。 -
「
navigate(-1)有什么坑?」
最大的坑是「没有历史记录时它什么都不做」——如果用户是「直接打开链接」或「从外部跳进来」的,后退可能会退出应用或没有反应。稳妥的做法是判断location.key(React Router 用它标识历史条目的唯一性,值为'default'表示「会话中的第一个条目」):如果是'default'就跳到一个明确的兜底路径(如首页),否则才navigate(-1)。 -
「怎么在 React 之外(如 axios 拦截器)做跳转?」
因为 Hooks 只能在组件/Hook 内调用,所以需要「桥接」。两种做法:① 全局变量持有navigate——在一个根组件里globalNavigate = useNavigate(),然后在拦截器里调用globalNavigate?.('/login')(简单但有点「副作用式」);② 事件总线——拦截器里window.dispatchEvent(new CustomEvent('auth:expired')),由一个根组件监听并执行跳转(更干净、更可测)。也有方案是「用 store 存一个 redirect 状态,由路由组件响应」。
一句话速记
编程式导航用
useNavigate()(navigate('/path')/navigate(-1)/navigate('/path', { replace: true, state })/ 相对路径'detail'、'..');渲染时的重定向用<Navigate replace />(绝不能在渲染中调用navigate);登录成功、提交成功后要加replace(否则后退回到登录页);state传不进 URL 的临时数据(刷新仍在但不可分享,必须有兜底),需要分享的状态放 URL;navigate(-1)在「无历史」时无效(用location.key === 'default'兜底);先await再navigate;React 之外跳转要「全局持有 navigate」或「事件总线」桥接;useBlocker只在数据路由下可用(未保存提示)。



最新评论
读过书不知道欧·亨利的人少。教科书上选文有
这小生活不错呀
不错,必须顶一下!
看着你还在坚持,很好
看来忙了也没时间更新博客了
NIce。学习了。。。。
网站不错!!!!
简洁实用,好文章!