Skip to content

React Router 路由实战总结:跳转方式与参数接收 ​

React Router 是 React 官方生态中最主流的路由库(当前主流版本为 v6/v7,本文基于 v6+ 的 Hooks API)。核心理念:路由即组件,路径变化驱动 UI 变化。本文总结路由的几种跳转方式,以及 useParams / useLocation / useSearchParams 三种接收参数的方法。


目录 ​

  1. 安装与基础配置
  2. 路由跳转的几种方式
  3. useNavigate 编程式跳转详解
  4. useParams 接收路径参数
  5. useSearchParams 接收查询参数
  6. useLocation 接收 state 参数
  7. 三种传参方式对比与选型
  8. 注意事项与常见坑

1. 安装与基础配置 ​

bash
npm install react-router-dom
# 或
pnpm add react-router-dom

注意:v6 中 Web 端用的是 react-router-dom 包(它内部依赖 react-router),所有 Hooks 都从 react-router-dom 导入。

1.1 声明路由表 ​

tsx
// main.tsx
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import Home from "./pages/Home";
import UserDetail from "./pages/UserDetail";
import Search from "./pages/Search";
import NotFound from "./pages/NotFound";

const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  // :id 是动态路径参数
  { path: "/user/:id", element: <UserDetail /> },
  { path: "/search", element: <Search /> },
  // 兜底 404
  { path: "*", element: <NotFound /> },
]);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router} />,
);

1.2 嵌套路由(Outlet) ​

tsx
// 父路由布局组件:子路由内容渲染在 <Outlet /> 位置
import { Outlet, Link } from "react-router-dom";

function Layout() {
  return (
    <div>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/user/1">用户</Link>
      </nav>
      <main>
        <Outlet /> {/* 子路由渲染出口 */}
      </main>
    </div>
  );
}

// 路由配置
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <Home /> }, // index:父路径的默认子路由
      { path: "user/:id", element: <UserDetail /> },
      { path: "search", element: <Search /> },
    ],
  },
]);

旧式写法 BrowserRouter + Routes + Route 仍然可用,功能等价,createBrowserRouter 额外支持 loader/action 数据 API。


2. 路由跳转的几种方式 ​

React Router 中的跳转方式可以归纳为 四种:

方式适用场景是否可传参
<Link> 声明式链接页面内普通导航(菜单、卡片、文字链)✅
<NavLink> 带激活态链接导航栏,需要高亮当前项✅
useNavigate 编程式跳转事件回调中跳转(登录成功、表单提交后)✅
<Navigate> 组件重定向渲染期条件跳转(路由守卫、鉴权拦截)✅

最基础的跳转,渲染为 <a> 标签,点击不刷新页面。

tsx
import { Link } from "react-router-dom";

function Home() {
  return (
    <div>
      {/* 基础跳转 */}
      <Link to="/user/1">查看用户 1</Link>

      {/* 带查询参数 */}
      <Link to="/search?keyword=react&page=2">搜索结果</Link>

      {/* 带 state 隐式传参 */}
      <Link to="/user/1" state={{ from: "home", tab: "posts" }}>
        用户详情(带 state)
      </Link>

      {/* 替换历史记录(不留下浏览记录) */}
      <Link to="/login" replace>
        去登录
      </Link>
    </div>
  );
}

与 <Link> 用法相同,但会自动感知"当前是否处于该路由",常用于菜单高亮。

tsx
import { NavLink } from "react-router-dom";

function Menu() {
  return (
    <nav>
      <NavLink
        to="/"
        // className 接收函数,isActive 表示是否激活
        className={({ isActive }) =>
          isActive ? "menu-item active" : "menu-item"
        }
      >
        首页
      </NavLink>

      <NavLink
        to="/search"
        // style 也支持函数形式
        style={({ isActive }) => ({
          color: isActive ? "#1677ff" : "#333",
          fontWeight: isActive ? "bold" : "normal",
        })}
      >
        搜索
      </NavLink>
    </nav>
  );
}

2.3 useNavigate 编程式跳转(重点,下一节详解) ​

tsx
const navigate = useNavigate();
navigate("/user/1"); // 普通跳转
navigate(-1); // 后退

2.4 <Navigate> 组件重定向 ​

在渲染期间跳转,典型用途是路由守卫:

tsx
import { Navigate } from "react-router-dom";

function ProtectedPage() {
  const isLoggedIn = checkLogin();

  // 未登录:渲染时直接重定向到登录页,并记录来源
  if (!isLoggedIn) {
    return <Navigate to="/login" replace state={{ from: "/protected" }} />;
  }

  return <div>需要登录才能看到的内容</div>;
}

3. useNavigate 编程式跳转详解 ​

useNavigate 返回一个跳转函数,用于事件处理、异步回调中跳转页面——这是与 <Link> 的本质区别:<Link> 靠用户点击,navigate 靠代码触发。

tsx
import { useNavigate } from "react-router-dom";

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

  const handleLogin = async () => {
    await loginApi();
    // 登录成功后跳转,replace 防止用户点"后退"又回到登录页
    navigate("/", { replace: true });
  };

  return <button onClick={handleLogin}>登录</button>;
}

3.1 基本用法 ​

tsx
const navigate = useNavigate();

// 跳转到指定路径(会向 history 压入一条记录)
navigate("/user/1");

// 带查询参数
navigate("/search?keyword=react&page=2");

// 相对路径跳转(基于当前路由)
navigate("detail"); // /user → /user/detail
navigate("../list"); // 回到上一级

// 数字参数:前进 / 后退
navigate(-1); // 后退一步(相当于浏览器后退键)
navigate(-2); // 后退两步
navigate(1); // 前进一步

3.2 第二个参数:跳转选项 ​

tsx
navigate("/user/1", {
  // ✅ state:隐式传参,不出现在 URL 中
  state: { userName: "张三", from: "list" },

  // ✅ replace:替换当前历史记录,而不是新增(跳转后无法"后退"回来)
  replace: true,
});

3.3 完整示例:登录跳转并回跳来源页 ​

tsx
function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();

  const handleLogin = async () => {
    await loginApi();
    // 从守卫传过来的 state 里取来源路径,回跳;没有则回首页
    const from = location.state?.from ?? "/";
    navigate(from, { replace: true });
  };

  return <button onClick={handleLogin}>登录</button>;
}

⚠️ useNavigate 必须在路由上下文中调用(组件必须被 <RouterProvider> / <BrowserRouter> 包裹),在普通工具函数里无法使用。组件外想跳转需通过 router 实例(router.navigate())。


4. useParams 接收路径参数 ​

用于接收 URL 路径中的动态段,即路由配置里 :xxx 声明的部分。

4.1 定义与接收 ​

tsx
// 路由配置:定义动态段 :id
{ path: "/user/:id", element: <UserDetail /> }
tsx
// 访问 /user/42 时
import { useParams } from "react-router-dom";

function UserDetail() {
  // params 是一个对象,key 对应路由中的 :id
  const { id } = useParams();

  return <h1>用户 ID:{id}</h1>; // 输出:用户 ID:42
}

4.2 多个参数 ​

tsx
// 路由配置
{ path: "/user/:userId/post/:postId", element: <PostDetail /> }
tsx
// 访问 /user/7/post/99 时
function PostDetail() {
  const { userId, postId } = useParams();
  // userId = "7",postId = "99"

  return (
    <p>
      用户 {userId} 的文章 {postId}
    </p>
  );
}

4.3 注意点 ​

tsx
const { id } = useParams();

// ⚠️ 1. 参数值永远是 string 类型
console.log(typeof id); // "string"
const numId = Number(id); // 需要数字时手动转换

// ⚠️ 2. TypeScript 中建议标注类型(key 可能不存在,值为 string | undefined)
const { id } = useParams<{ id: string }>();

// ⚠️ 3. 参数变化会触发组件重渲染,可用 useEffect 监听
useEffect(() => {
  fetchUser(id); // 从 /user/1 切到 /user/2 时重新请求
}, [id]);

特点总结:

  • 参数体现在 URL 路径中:/user/42
  • 刷新页面、分享链接、收藏书签后参数依然有效
  • 适合传标识性、必需的参数(资源 ID、分类名)

5. useSearchParams 接收查询参数 ​

用于接收 URL 中 ? 后面的查询字符串(query string),用法类似 useState。

5.1 基本读取 ​

tsx
// 访问 /search?keyword=react&page=2 时
import { useSearchParams } from "react-router-dom";

function SearchPage() {
  // searchParams 是一个 URLSearchParams 实例
  const [searchParams] = useSearchParams();

  const keyword = searchParams.get("keyword"); // "react"
  const page = searchParams.get("page"); // "2"(⚠️ 字符串)

  return (
    <p>
      搜索「{keyword}」第 {page} 页
    </p>
  );
}

5.2 常用读取方法 ​

tsx
const [searchParams] = useSearchParams();

searchParams.get("keyword"); // 取单个值,不存在返回 null
searchParams.getAll("tag"); // 取多个同名参数:?tag=a&tag=b → ["a", "b"]
searchParams.has("keyword"); // 是否存在 → true / false
searchParams.get("page") ?? "1"; // 提供默认值
Number(searchParams.get("page")); // 转数字

5.3 修改查询参数(setSearchParams) ​

tsx
const [searchParams, setSearchParams] = useSearchParams();

// 整体替换查询参数(会触发路由变化,URL 同步更新)
setSearchParams({ keyword: "vue", page: "1" });
// URL 变为 /search?keyword=vue&page=1

// 在现有参数基础上修改(推荐写法)
setSearchParams((prev) => {
  prev.set("page", "3"); // 只改 page,保留其他参数
  return prev;
});

// 替换历史记录而不是新增
setSearchParams({ keyword: "vue" }, { replace: true });

5.4 完整示例:搜索 + 分页 ​

tsx
function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const keyword = searchParams.get("keyword") ?? "";
  const page = Number(searchParams.get("page") ?? "1");

  return (
    <div>
      <input
        value={keyword}
        onChange={(e) =>
          // 输入变化同步到 URL,页码重置为 1
          setSearchParams({ keyword: e.target.value, page: "1" })
        }
      />
      <button
        onClick={() => setSearchParams({ keyword, page: String(page + 1) })}
      >
        下一页(当前第 {page} 页)
      </button>
    </div>
  );
}

特点总结:

  • 参数体现在 URL 查询串中:/search?keyword=react&page=2
  • 刷新、分享、收藏后参数依然有效
  • URL 即状态:参数变化驱动组件更新,天然支持前进/后退
  • 适合传可选的、过滤类参数(搜索词、页码、筛选条件、排序方式)

6. useLocation 接收 state 参数 ​

useLocation 返回当前路由的位置对象,既能拿 URL 信息,也能拿跳转时通过 state 传入的隐式参数。

6.1 location 对象结构 ​

tsx
import { useLocation } from "react-router-dom";

function AnyPage() {
  const location = useLocation();

  console.log(location.pathname); // "/user/1"        —— 路径
  console.log(location.search); // "?tab=posts"     —— 查询串(字符串)
  console.log(location.hash); // "#section1"      —— 锚点
  console.log(location.state); // { from: "home" } —— 跳转时传的隐式参数
  console.log(location.key); // "abc123"         —— 该条历史记录的唯一 key
}

6.2 发送方:通过 state 传参 ​

tsx
// 方式一:Link
<Link to="/user/1" state={{ userName: "张三", role: "admin" }}>
  查看用户
</Link>;

// 方式二:useNavigate
navigate("/user/1", { state: { userName: "张三", role: "admin" } });

6.3 接收方:通过 location.state 取参 ​

tsx
function UserDetail() {
  const location = useLocation();
  const state = location.state as { userName?: string; role?: string } | null;

  return (
    <div>
      {/* ⚠️ state 可能为 null(比如用户直接输入 URL 访问),要做空值处理 */}
      <p>用户名:{state?.userName ?? "未知"}</p>
      <p>角色:{state?.role ?? "无"}</p>
    </div>
  );
}

6.4 典型应用:详情页带列表数据预览 ​

tsx
// 列表页:跳转时把整个 item 塞过去,详情页秒开不用等请求
function UserList({ users }: { users: User[] }) {
  const navigate = useNavigate();

  return (
    <ul>
      {users.map((u) => (
        <li
          key={u.id}
          onClick={() => navigate(`/user/${u.id}`, { state: { preview: u } })}
        >
          {u.name}
        </li>
      ))}
    </ul>
  );
}

// 详情页:先用 state 里的预览数据渲染,再用 id 拉取完整数据
function UserDetail() {
  const { id } = useParams();
  const { state } = useLocation();
  const preview = (state as { preview?: User } | null)?.preview;

  const [detail, setDetail] = useState<User | null>(preview ?? null);

  useEffect(() => {
    fetchUser(id).then(setDetail); // 后台补全数据
  }, [id]);

  if (!detail) return <p>加载中...</p>;
  return <h1>{detail.name}</h1>;
}

特点总结:

  • 参数不出现在 URL 中,用户看不到、改不了
  • 适合传敏感的、临时的、结构化的数据(对象、数组都能传,不用拼字符串)
  • ⚠️ 刷新页面后 state 会保留(history API 会保存),但新开标签页 / 直接输入 URL 访问时为 null,所以 state 只能作为"增强",不能作为唯一数据来源
  • 页面间不共享 URL 也能传参

7. 三种传参方式对比与选型 ​

维度useParams 路径参数useSearchParams 查询参数useLocation state 参数
参数位置URL 路径:/user/42URL 查询串:?page=2不在 URL 中,存于 history
值类型永远 string永远 string任意类型(对象、数组)
刷新后有效✅✅✅(但新开标签/直输 URL 失效)
可分享/收藏✅✅❌
用户可见可改✅✅❌(相对隐蔽)
适合场景资源 ID、必需的标识搜索词、分页、筛选、排序敏感数据、临时状态、来源标记、预览数据
典型例子/user/:id /post/:slug/list?page=2&sort=hot登录后回跳来源、防直访标记

选型口诀:

  • 这个参数决定页面显示谁 → 用路径参数(useParams)
  • 这个参数影响页面怎么显示(过滤、排序、翻页) → 用查询参数(useSearchParams)
  • 这个参数不该出现在地址栏(敏感、临时、结构化) → 用 state(useLocation)

实际项目中经常组合使用:

tsx
// URL:/user/42?tab=posts,state 带预览数据
navigate(`/user/42?tab=posts`, { state: { preview: user } });

function UserDetail() {
  const { id } = useParams(); // 42 → 拉取数据
  const [searchParams] = useSearchParams(); // tab=posts → 控制 Tab 显示
  const { state } = useLocation(); // preview → 秒开预览
}

8. 注意事项与常见坑 ​

8.1 参数都是字符串,记得转换 ​

tsx
const { id } = useParams();
id + 1; // ❌ "42" + 1 = "421"
Number(id) + 1; // ✅ 43

8.2 state 可能为 null,务必做空值兜底 ​

tsx
const { state } = useLocation();
console.log(state.from); // ❌ 直接访问 URL 进入时 state 为 null,直接报错

const from = state?.from ?? "/"; // ✅

8.3 useSearchParams 的 set 是整体替换 ​

tsx
// 当前 URL:?keyword=react&page=2
setSearchParams({ page: "3" });
// ❌ URL 变成 ?page=3 —— keyword 丢了!

// ✅ 基于旧值修改
setSearchParams((prev) => {
  prev.set("page", "3");
  return prev;
});

8.4 监听参数变化要放进依赖数组 ​

tsx
const { id } = useParams();

useEffect(() => {
  fetchUser(id);
}, []); // ❌ 只在挂载时执行,/user/1 → /user/2 不会重新请求

useEffect(() => {
  fetchUser(id);
}, [id]); // ✅

8.5 Hooks 必须在路由上下文内使用 ​

useNavigate / useParams / useLocation / useSearchParams 都依赖路由 Context,在未被 Router 包裹的组件或普通工具函数中调用会直接报错。组件外跳转请用 router 实例:

tsx
import { router } from "./router";

// 请求拦截器等组件外场景
router.navigate("/login");

8.6 replace 的正确使用场景 ​

  • 登录成功跳转首页:replace: true(防止后退回到登录页)
  • 重定向/守卫:replace: true(历史里不留中间页)
  • 轮播、向导步骤:视产品需求,一般步骤页用 replace 避免历史堆栈污染

8.7 v6 与 v5 的差异提醒 ​

如果看的是旧教程,注意这些 API 已废弃:

v5 写法v6 写法
useHistory() / history.push()useNavigate() / navigate()
useHistory() / history.replace()navigate(path, { replace: true })
<Switch><Routes>
<Route component={...}><Route element={...}>
<Redirect><Navigate>
withRouter 高阶组件已移除,直接用 Hooks
useRouteMatch用相对路径或 useMatch

附:完整最小可运行示例 ​

tsx
// main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import {
  createBrowserRouter,
  RouterProvider,
  Link,
  NavLink,
  Outlet,
  useNavigate,
  useParams,
  useLocation,
  useSearchParams,
} from "react-router-dom";

// ---------- 布局 ----------
function Layout() {
  return (
    <div style={{ fontFamily: "sans-serif", padding: 24 }}>
      <nav style={{ display: "flex", gap: 12, marginBottom: 16 }}>
        <NavLink
          to="/"
          style={({ isActive }) => ({
            fontWeight: isActive ? "bold" : "normal",
          })}
        >
          首页
        </NavLink>
        <NavLink
          to="/search"
          style={({ isActive }) => ({
            fontWeight: isActive ? "bold" : "normal",
          })}
        >
          搜索
        </NavLink>
      </nav>
      <Outlet />
    </div>
  );
}

// ---------- 首页:演示各种跳转 ----------
function Home() {
  const navigate = useNavigate();

  return (
    <div>
      <h2>首页</h2>
      {/* 1. Link 跳转 */}
      <p>
        <Link to="/user/42">Link → /user/42</Link>
      </p>
      {/* 2. Link 带 state */}
      <p>
        <Link to="/user/42" state={{ from: "home" }}>
          Link + state → /user/42
        </Link>
      </p>
      {/* 3. navigate 跳转 */}
      <button
        onClick={() =>
          navigate("/user/42?tab=posts", { state: { from: "home" } })
        }
      >
        navigate → /user/42?tab=posts(带 state)
      </button>
      <button onClick={() => navigate(-1)} style={{ marginLeft: 8 }}>
        后退
      </button>
    </div>
  );
}

// ---------- 用户详情:演示三种接收参数 ----------
function UserDetail() {
  const { id } = useParams(); // ① 路径参数
  const [searchParams] = useSearchParams(); // ② 查询参数
  const { state } = useLocation() as { state: { from?: string } | null }; // ③ state 参数

  return (
    <div>
      <h2>用户详情</h2>
      <p>useParams → id:{id}</p>
      <p>useSearchParams → tab:{searchParams.get("tab") ?? "未指定"}</p>
      <p>
        useLocation → state.from:
        {state?.from ?? "无(可能是直接输入 URL 进入)"}
      </p>
    </div>
  );
}

// ---------- 搜索页:演示 useSearchParams 读写 ----------
function Search() {
  const [searchParams, setSearchParams] = useSearchParams();
  const keyword = searchParams.get("keyword") ?? "";

  return (
    <div>
      <h2>搜索</h2>
      <input
        value={keyword}
        placeholder="输入关键词"
        onChange={(e) => setSearchParams({ keyword: e.target.value })}
      />
      <p>当前 URL 查询串:{searchParams.toString() || "(空)"}</p>
    </div>
  );
}

// ---------- 路由表 ----------
const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: "user/:id", element: <UserDetail /> },
      { path: "search", element: <Search /> },
      { path: "*", element: <h2>404 页面不存在</h2> },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root")!).render(
  <RouterProvider router={router} />,
);