人人都会AI编程

12.3 编程式导航与路由跳转

更新时间:2026-07-11

在 React Router v6 中,除了使用 <Link><NavLink> 组件实现声明式导航,大多数场景下你还需要通过代码控制跳转——例如表单提交成功后跳转到列表页、权限校验失败后跳转到登录页等。这就是编程式导航的用武之地。

12.3.1 核心 Hook:useNavigate

useNavigate 是 React Router v6 提供的一个 Hook,它返回一个导航函数,调用该函数即可执行路由跳转。

import { useNavigate } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = async (formData) => {
    const result = await loginApi(formData);
    if (result.success) {
      // 跳转到首页
      navigate('/dashboard');
    } else {
      alert('登录失败');
    }
  };

  return <form onSubmit={handleLogin}>...</form>;
}

navigate 函数可以接受两种参数形式:

  • 字符串路径:直接跳转到目标路由。
  • 数字 delta:类似浏览器的 history.go(),指定前进或后退的步数。例如 navigate(-1) 回到上一页。
// 返回上一页
navigate(-1);

// 前进一页
navigate(1);

12.3.2 携带参数:query 参数与路径参数

在实际业务中,跳转时经常需要传递参数,比如搜索关键词、分页信息、资源 ID 等。

1. 传递 search 参数(查询字符串)

你可以直接在路径中拼接查询字符串,但更推荐使用 URLSearchParams 或 React Router 提供的方式构造对象,以保持可读性。

方法一:字符串拼接

navigate(`/search?keyword=${encodeURIComponent(keyword)}&page=1`);

方法二:使用 createSearchParams 辅助函数

import { useNavigate, createSearchParams } from 'react-router-dom';

function SearchBar() {
  const navigate = useNavigate();

  const handleSearch = (keyword) => {
    navigate({
      pathname: '/search',
      search: createSearchParams({ keyword, page: '1' }).toString(),
    });
  };
}

在目标组件中,通过 useSearchParams 读取这些参数:

import { useSearchParams } from 'react-router-dom';

function SearchResults() {
  const [searchParams] = useSearchParams();
  const keyword = searchParams.get('keyword');
  const page = searchParams.get('page') || '1';
}

2. 传递路径参数(动态路由)

如果路由配置中定义了动态参数,如 /users/:id,可以直接拼接 URL:

navigate(`/users/${userId}`);

或者同样使用对象形式:

navigate(`/users/${userId}`);
// 或者
navigate({ pathname: `/users/${userId}` });

在目标组件中通过 useParams 提取:

import { useParams } from 'react-router-dom';

function UserDetail() {
  const { id } = useParams();
}

3. 传递 state(隐藏状态)

有时你需要在不暴露在 URL 中的情况下传递数据,比如多步骤表单的中间数据。React Router 允许通过 state 选项传递任意对象,这在跳转到新路由时非常有用。

navigate('/checkout', { state: { fromCart: true, items } });

在目标组件中使用 useLocation 读取:

import { useLocation } from 'react-router-dom';

function Checkout() {
  const location = useLocation();
  const { fromCart, items } = location.state || {};
}

注意state 数据不会存储在 URL 中,页面刷新后会丢失,因此适用于临时会话内的数据传递。

12.3.3 替换当前历史记录(replace)

默认情况下,navigate 会向浏览历史栈中添加一条新记录,用户点击“后退”时可以回到之前的页面。但在某些场景(如登录后跳转到首页、表单提交后跳转到列表页),你不希望用户能够通过“后退”回到登录页或已提交的表单,这时应使用 replace 选项。

// 登录成功后替换当前历史记录,而不是新增一条
navigate('/dashboard', { replace: true });

等价于 <Link replace to="/dashboard"> 的行为。用户从 Dashboard 页面点击后退将跳过登录页,直接回到登录前的页面。

12.3.4 编程式导航的常见实践

1. 全局权限拦截

配合路由守卫,可以在导航前进行权限校验,校验不通过时重定向到登录页。

import { useNavigate } from 'react-router-dom';

function useAuthRedirect() {
  const navigate = useNavigate();

  const redirectIfUnauthenticated = (isAuthenticated) => {
    if (!isAuthenticated) {
      navigate('/login', { replace: true, state: { from: location.pathname } });
    }
  };

  return { redirectIfUnauthenticated };
}

在上面的例子中,我们将当前路径通过 state 传递给登录页,这样登录成功后可以再导航回原来的目标页面。

2. 表单提交后的跳转

const navigate = useNavigate();

const handleSubmit = async (data) => {
  await createPost(data);
  navigate(`/posts/${data.id}`, { replace: true });
};

3. 全局 404 处理与回退

function NotFoundPage() {
  const navigate = useNavigate();

  return (
    <div>
      <h1>404 - 页面未找到</h1>
      <button onClick={() => navigate('/')}>回到首页</button>
      <button onClick={() => navigate(-1)}>返回上一页</button>
    </div>
  );
}

12.3.5 useNavigate 与 v5 的 history 对象对比

如果你是从 React Router v5 迁移过来的,需要注意几个关键变化:

| v5 (history 对象) | v6 (useNavigate) | 说明 |
|------------------|------------------|------|
| history.push('/path') | navigate('/path') | 跳转并新增历史记录 |
| history.replace('/path') | navigate('/path', { replace: true }) | 替换当前历史记录 |
| history.goBack() | navigate(-1) | 后退 |
| history.goForward() | navigate(1) | 前进 |
| history.push('/path', state) | navigate('/path', { state }) | 携带隐藏状态 |

v6 的 API 更加精简,Hook 的使用也完全符合函数组件的规范。

12.3.6 注意事项

  • 必须在 Router 内部使用useNavigate 只能在 <BrowserRouter><MemoryRouter> 等路由组件的子组件中调用,否则会报错。
  • 避免在渲染期间直接导航:不要在组件的顶层直接调用 navigate()(例如在渲染阶段进行条件导航),因为这可能导致副作用异常。此类逻辑应放在 useEffect 或事件处理函数中。
  • 导航与状态更新顺序:调用 navigate 后,React 会立即计划一次路由更新,但不会阻塞当前函数执行。后续的 setState 可能会在组件卸载前执行,需要注意内存泄漏问题(如清理副作用)。

编程式导航是构建任何非平凡应用的基础能力。掌握 useNavigate 和各种参数传递方式,能让你在业务逻辑中自如地控制用户的跳转流程,提供更为流畅的交互体验。配合 React Router v6 的其他特性(如 Loader/Action),可以进一步实现数据驱动的导航,这些将在后续章节深入探讨。