人人都会AI编程

12.2 基础配置:路由映射、嵌套路由、动态路由、重定向

更新时间:2026-07-10

动态路由是现代 Web 应用最常见的需求之一:同一个路由模板匹配多个类似的 URL,同时提取出变化部分用于数据获取或逻辑处理。React Router v6 提供了简洁且类型安全的 API 来处理动态路由、路由参数和查询参数。

动态路由与路由参数

定义动态路由

在路由路径中使用 :参数名 声明一个动态段,冒号后面的标识符即为参数的名称。例如,一个用户详情页可能对应 /users/123/users/456 等不同 URL。

// router/index.jsx
import { createBrowserRouter } from 'react-router-dom';
import UserDetail from './UserDetail';

const router = createBrowserRouter([
  {
    path: '/users/:userId',
    element: <UserDetail />,
  },
]);

上面的 :userId 会匹配 /users/ 之后的任意单一路径段,并将该段的值作为参数 userId 注入到组件中。

在组件中获取路由参数:useParams

React Router v6 使用 useParams Hook 读取当前路由的动态参数,返回一个键值对对象,键名为声明的参数名。

// UserDetail.jsx
import { useParams } from 'react-router-dom';

function UserDetail() {
  const { userId } = useParams(); // userId 为 string 类型
  console.log(userId); // 如果 URL 是 /users/42,这里输出 "42"

  return <h1>用户详情:{userId}</h1>;
}

useParams 返回的都是字符串类型,因为 URL 本身就是文本。如果你需要将参数作为数字处理,记得进行类型转换。

可选参数与多段参数

React Router v6 原生不支持可选参数(如 /users/:id?),但可以通过定义多个路由路径或使用 * 通配符来变通实现。如果需要更复杂的模式匹配,可以考虑升级到未来版本或使用自定义匹配逻辑。

对于需要捕获多段路径的场景,可以使用通配符 ,它会匹配任意数量的路径段,并将这些段存储在 对应的键中。

{
  path: '/docs/*',
  element: <DocPage />,
}

function DocPage() {
  const { '*': splat } = useParams(); // 如果 URL 是 /docs/a/b,则 splat = "a/b"
}

查询参数(Query Parameters)

查询参数是 URL 问号(?)后面的键值对部分,例如 /search?keyword=react&page=2。React Router v6 提供了 useSearchParams Hook 来读写查询参数,其用法类似于 useState,但数据会同步到浏览器地址栏的查询字符串中。

读取查询参数

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

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

  return (
    <div>
      <p>搜索关键词:{keyword}</p>
      <p>当前页码:{page}</p>
    </div>
  );
}

searchParams 是一个 URLSearchParams 的实例,除了 get,还可以使用 getAll(获取数组参数)、hasforEach 等方法。

修改查询参数

useSearchParams 返回的第二个元素是更新函数,调用它会重新设置查询字符串并触发导航。你可以传入一个新的 URLSearchParams 对象、一个一般的对象(React Router 会自动序列化),或者一个回调函数来基于当前参数进行修改。

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();

  const updateKeyword = (newKeyword) => {
    setSearchParams({ keyword: newKeyword, page: String(1) }); // 重置页码
  };

  const goToPage = (pageNum) => {
    const params = new URLSearchParams(searchParams);
    params.set('page', String(pageNum));
    setSearchParams(params);
  };
}

需要注意,setSearchParams 的默认行为会触发路由导航(可以理解为一个 useNavigate 的便捷封装),因此它会更新浏览器历史记录栈,并且组件会重新渲染。如果你不希望增加历史记录条目,可以传入 { replace: true } 选项。

路由参数 vs 查询参数

两者都用于在 URL 中传递数据,但用途不同,选择时可以参考以下原则:

| 类型 | 用途 | 示例 |
|------|------|------|
| 路由参数 | 指定资源标识,通常必填,是资源路径的一部分 | /users/zhangsan |
| 查询参数 | 提供非必需的过滤、排序、分页等辅助信息 | /users?role=admin&page=2 |

一个具体的业务场景:用户列表页需要分页和筛选,那么可以设计成:

路径:/users
查询参数:?page=1&keyword=react&status=active

但如果是一个编辑页,则需要定位到具体的实体,此时路由参数更合适:

路径:/users/:id/edit

动态路由与加载器结合的实际模式

在 React Router v6.4+ 的数据路由中,我们经常需要在加载数据时读取路由参数或查询参数。loader 函数可以接收一个包含 paramsrequest 的对象,让你能够在渲染组件之前就拿到这些值。

{
  path: '/users/:userId',
  loader: async ({ params }) => {
    const user = await fetchUser(params.userId);
    return { user };
  },
  element: <UserDetail />,
}

function UserDetail() {
  const { user } = useLoaderData();
  // 无需手动 useParams,数据已在加载器中根据参数获取
}

类似地,在 loader 里也可以利用 request.url 解析查询参数:

{
  path: '/users',
  loader: async ({ request }) => {
    const url = new URL(request.url);
    const page = url.searchParams.get('page') || 1;
    const data = await fetchUsers(page);
    return data;
  },
}

这种方式让数据获取与组件渲染解耦,并且数据在加载阶段就已经准备就绪,避免了先渲染再请求的“闪烁”问题。

常见陷阱与注意事项

  1. 参数类型useParams 返回的值都是字符串,请务必在使用时转换成需要的类型(如 Number)。
  2. 查询参数的竞态:如果连续快速调用 setSearchParams,React Router 会对它们进行合并,但这可能导致组件不必要的多次渲染。使用函数式更新或批量修改可以减少次数。
  3. 参数变化时的副作用:当路由参数改变时(如从 /users/1 跳转到 /users/2),组件默认会被卸载并重新挂载(如果使用了相同的组件但 key 不同),或者复用。如果你想在参数变化时重新获取数据,可以用 useEffect 监听参数变化,但更推荐使用 loader 机制,它会自动处理参数变化后的数据重新请求。
  4. URL 安全与编码:查询参数中的特殊字符需要编码,URLSearchParamssetSearchParams 会自动处理,但你直接拼接字符串时需留意。

掌握动态路由和查询参数的用法,你就能构建出符合 RESTful 风格的、深度可链接的 React 应用,让用户可以通过 URL 直接访问特定的界面状态。