☰
React Router Declarative Mode 完整指南:用 `<BrowserRouter>` 与 `<Routes>` 实现最简声明式路由
2026/11/1 9:00:32 网站建设 项目流程

React Router Declarative Mode 完整指南:用<BrowserRouter>与<Routes>实现最简声明式路由

【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router

本指南聚焦 React Router 三种使用模式中最简单的Declarative Mode(声明式模式):它依托<BrowserRouter>等顶层 Router 组件与 JSX 形态的<Routes>/<Route>配置,把 URL 匹配到 UI 组件,并提供应用内导航与 URL 值读取能力。读完本文将掌握声明式应用的安装与骨架搭建、嵌套/布局/索引路由的编排、<Link>/<NavLink>/useNavigate的导航规范、useParams/useSearchParams/useLocation的 URL 值使用边界,以及它与 Data Mode、Framework Mode 的分界线与升级路径。文中全部细节均来自当前仓库的官方文档(docs/start/modes.md、docs/start/declarative 目录)与 packages/react-router 源码。

什么是 Declarative Mode

React Router 是一个"多策略"路由器:同一套 API 之下有Declarative(声明式)、Data(数据)、Framework(框架)三种主要使用方式。正如 docs/start/modes.md 所描述的,三种模式的 API 特性是additive(叠加式)的——从 Declarative 升级到 Data 再到 Framework,本质是"以放弃部分架构控制权为代价,换取更多开箱即用的能力"。因此选哪种模式,取决于你希望 React Router 帮你做多少事,而不是你想要怎样的部署形态。

Declarative Mode 是其中最简单的一种:

  • 使用<BrowserRouter>、<HashRouter>、<MemoryRouter>这类"顶层路由组件"包裹应用;
  • 使用 JSX 形式的路由配置<Routes>+<Route>;
  • 提供 URL 匹配组件、应用内导航和 active 状态等基础能力(<Link>、useNavigate、useLocation等);
  • 不提供loader、action、fetcher 以及>import { createBrowserRouter, RouterProvider } from "react-router"; let router = createBrowserRouter([ { path: "/", Component: Root, loader: loadRootData, }, ]); ReactDOM.createRoot(root).render(<RouterProvider router={router} />);

    而 Framework Mode 则在 Vite 配置中以routes.ts声明路由文件,并配合+types自动生成类型。声明式模式下这些都用不到——这正是它的价值:最小依赖、最少概念。

    什么场景适合选 Declarative Mode

    docs/start/modes.md 给出的决策建议是,出现以下情况优先考虑 Declarative Mode:

    • 想把 React Router 用得尽可能简单;
    • 从早期 React Router 版本迁移而来,已经习惯<BrowserRouter>的写法;
    • 数据层要么天然跳过 pending 状态(如 local-first、后台数据同步/复制型架构),要么已经拥有自己的一整套 pending/loading 抽象,不需要路由层再来一套;
    • 从 Create React App 迁移而来(不过这种场景官方也提示"可能更应该考虑 Framework Mode")。

    快速开始:安装与最小声明式应用

    文档 docs/start/declarative/installation.md 给出了一条最直接的起步路径。先用 Vite 官方模板创建一个 React 应用(也可用你喜欢的任何方式初始化 React 工程):

    npx create-vite@latest

    随后安装 React Router:

    npm i react-router

    最后在应用根部渲染一个<BrowserRouter>包裹整个应用:

    import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router"; import App from "./app"; const root = document.getElementById("root"); ReactDOM.createRoot(root).render( <BrowserRouter> <App /> </BrowserRouter>, );

    一个声明式应用通常拥有两层结构:顶层 Router 组件(负责订阅浏览器历史、提供 Router Context)+<Routes>路由表(负责把当前 URL 匹配到具体组件)。作为对照,lib/dom/lib.tsx 中<BrowserRouter>、<HashRouter>都实现在 DOM 层(lib.tsx#L826、lib.tsx#L917),分别面向historyAPI 与 URL hash 两种历史存储;而<MemoryRouter>(components.tsx#L799)在内存中维护历史栈,适合测试与无浏览器环境。三者都渲染同样的 Router Context,因此<App>内部的路由代码可以无缝切换。

    声明式路由的标准骨架

    当一个应用被判定为 Declarative Mode 后,最常见的骨架如下(摘自 .agents/skills/react-router/references/declarative-mode.md,也是官方示例react-router/docs/start/declarative/routing.md的推荐写法):

    import { BrowserRouter, Routes, Route } from "react-router"; function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Home />} /> <Route path="about" element={<About />} /> <Route path="dashboard" element={<DashboardLayout />}> <Route index element={<DashboardHome />} /> <Route path="settings" element={<Settings />} /> </Route> </Routes> </BrowserRouter> ); }

    在代码识别层面,声明式应用通常围绕以下 API 展开(它们也正是该模式文档中明确列出的识别清单):

    • <BrowserRouter>/<HashRouter>/<MemoryRouter>
    • <Routes>/<Route>
    • element={<Component />}
    • useRoutes

    注意,<Route>组件本身也支持loader、action、lazy、Component等属性(详见 docs/api/components/Route.md),但在声明式路由器中不要给路由对象添加 loader/action——那是 Data/Framework Mode 的领域。

    路由配置:从嵌套到 Splat

    在动手编辑路由前,文档要求先通读 docs/start/declarative/routing.md,并遵守以下规则:

    • 使用<Routes>与<Route>完成路由配置;
    • 用嵌套路由 +<Outlet>实现共享布局;
    • 用 index 路由充当父路由的默认子 UI;
    • 按声明式路由文档使用路由参数(params)与 splat;
    • 不要往声明式路由器里塞路由对象级 loader/action。

    嵌套路由与 Outlet

    父路由的 path 会自动拼接进子路由,因此下面的配置同时产生/dashboard与/dashboard/settings两个 URL:

    <Routes> <Route path="dashboard" element={<Dashboard />}> <Route index element={<Home />} /> <Route path="settings" element={<Settings />} /> </Route> </Routes>

    子路由通过父组件中的<Outlet/>渲染:

    import { Outlet } from "react-router"; export default function Dashboard() { return ( <div> <h1>Dashboard</h1> {/* 这里要么是 <Home/> 要么是 <Settings/> */} <Outlet /> </div> ); }

    Layout Routes(无 path 的嵌套)

    <Route>不写path时就成为 layout route:只为子路由建立新的嵌套层级,但不向 URL 增加任何段。下例用<MarketingLayout>和<ProjectsLayout>共享外壳、却不改变 URL 语义:

    <Routes> <Route element={<MarketingLayout />}> <Route index element={<MarketingHome />} /> <Route path="contact" element={<Contact />} /> </Route> <Route path="projects"> <Route index element={<ProjectsHome />} /> <Route element={<ProjectsLayout />}> <Route path=":pid" element={<Project />} /> <Route path=":pid/edit" element={<EditProject />} /> </Route> </Route> </Routes>

    Index Routes

    Index 路由用index属性声明,渲染进父路由的<Outlet/>,扮演"父 URL 上的默认子页面":

    <Routes> <Route path="/" element={<Root />}> {/* 在 "/" 处渲染进 <Root> 的 outlet */} <Route index element={<Home />} /> <Route path="dashboard" element={<Dashboard />}> {/* 在 "/dashboard" 处渲染进 <Dashboard> 的 outlet */} <Route index element={<DashboardHome />} /> <Route path="settings" element={<Settings />} /> </Route> </Route> </Routes>

    index 路由不能有子路由;如果你确实需要"既有默认 UI 又有子路由",那应该改用 layout route。

    Route Prefixes

    一个带path但没有element的<Route>不会引入父级布局,只是给子路由统一加一个路径前缀:

    <Route path="projects"> <Route index element={<ProjectsHome />} /> <Route element={<ProjectsLayout />}> <Route path=":pid" element={<Project />} /> <Route path=":pid/edit" element={<EditProject />} /> </Route> </Route>

    Dynamic Segments(动态段)

    路径段以:开头即为动态段。匹配成功后,解析出的值会以params形式提供给useParams等 API:

    <Route path="teams/:teamId" element={<Team />} />
    import { useParams } from "react-router"; export default function Team() { let params = useParams(); // params.teamId }

    一个 path 可以同时有多个动态段:

    <Route path="/c/:categoryId/p/:productId" element={<Product />} />
    import { useParams } from "react-router"; export default function CategoryProduct() { let { categoryId, productId } = useParams(); // ... }

    一个实用提醒:同一 path 中所有动态段名必须唯一。因为params对象是逐步填充的,后出现的同名动态段会覆盖先前的值。

    Optional Segments(可选段)

    段尾加?可使该段可选,动态段与静态段都支持:

    <Route path=":lang?/categories" element={<Categories />} />
    <Route path="users/:userId/edit?" element={<User />} />

    Splats(通配段)

    以/*结尾的 path 会匹配/之后的一切字符(包括额外斜杠),俗称 catchall / star 段:

    <Route path="files/*" element={<File />} />
    let params = useParams(); // params["*"] 包含 files/ 之后的剩余 URL let filePath = params["*"];

    解构通配键时必须重命名,常见做法是命名为splat:

    let { "*": splat } = useParams();

    导航:Link、NavLink 与 useNavigate

    按 docs/start/declarative/navigating.md,用户导航应用的三大入口是<Link>、<NavLink>与useNavigate。规则如下:

    • 用户主动触发的应用内导航,用<Link>或<NavLink>;
    • 需要 active 样式时用<NavLink>;
    • 事件回调或 effect 中的命令式跳转,用useNavigate;
    • 除非故意触发整页刷新,否则内部导航不要使用裸<a href>。

    NavLink:内置 active 状态

    <NavLink>在激活时会自动挂上.active类名,配合 CSS 即可完成高亮:

    export function MyAppNav() { return ( <nav> <NavLink to="/" end> Home </NavLink> <NavLink to="/trending" end> Trending Concerts </NavLink> <NavLink to="/concerts">All Concerts</NavLink> <NavLink to="/account">Account</NavLink> </nav> ); }
    a.active { color: red; }

    className、style、children三种 props 都支持接收以 active 状态为参数的函数,便于做内联样式或条件渲染:

    // className <NavLink to="/messages" className={({ isActive }) => isActive ? "text-red-500" : "text-black" } > Messages </NavLink>
    // style <NavLink to="/messages" style={({ isActive }) => ({ color: isActive ? "red" : "black", })} > Messages </NavLink>
    // children <NavLink to="/message"> {({ isActive }) => ( <span className={isActive ? "active" : ""}> {isActive ? "👉" : ""} Tasks </span> )} </NavLink>

    Link:无 active 需求的普通链接

    不需要 active 样式时直接使用<Link>:

    import { Link } from "react-router"; export function LoggedOutMessage() { return ( <p> You've been logged out.{" "} <Link to="/login">Login again</Link> </p> ); }

    值得说明的是,<Link>的点击拦截逻辑在仓库中由 DOM 层的useLinkClickHandler承担(lib/dom/lib.tsx#L2211),它把内部导航统一走路由,而不是触发浏览器整页刷新——这正是"不要用裸<a>"这条规则的底层原因。组件BrowserRouter/HashRouter之所以同样位于该文件,也是因为它们与 DOM 的 history 深度绑定。

    useNavigate:用户不在场时的命令式导航

    普通场景优先Link/NavLink,因为它们默认带来更好的体验:键盘事件、无障碍标签、"在新窗口打开"、右键菜单等。useNavigate应保留给用户没有直接点击却需要跳转的场景,例如:

    • 表单提交完成后跳转;
    • 闲置超时后登出;
    • 计时类 UI(测验倒计时等)。
    import { useNavigate } from "react-router"; export function LoginPage() { let navigate = useNavigate(); return ( <> <MyHeader /> <MyLoginForm onSuccess={() => { navigate("/dashboard"); }} /> <MyFooter /> </> ); }

    URL 值:params、Search Params 与 Location

    修改参数、search params 或 location state 之前,文档要求先通读 docs/start/declarative/url-values.md 以及 docs/explanation/location.md。核心规则如下:

    • 动态路由参数用useParams;
    • 查询字符串状态用useSearchParams;
    • 当前 location 对象与导航状态用useLocation;
    • URL 参数都要校验与解析——它们本质是字符串,且可能缺失;
    • 除非刻意清空,否则不要丢弃无关的 search params。

    Route Params

    useParams返回动态段解析出的值。例如<Route path="/concerts/:city">中:city的值可通过useParams获取:

    import { useParams } from "react-router"; function City() { let { city } = useParams(); let data = useFakeDataLibrary(`/api/v2/cities/${city}`); // ... }

    在源码层面,useParams实现在 lib/hooks.tsx#L666,它从 Router Context 中读取当前匹配的路由层级并汇总参数;同文件还提供 useLocation(L159) 与 useRoutes(L755)(其核心逻辑在useRoutesImpl,L763)。行为验证可参考 packages/react-router/tests/useLocation-test.tsx 与 useParams-test.tsx 等测试。

    URL Search Params

    ?之后的部分属于 search params,useSearchParams返回标准的URLSearchParams实例:

    function SearchResults() { let [searchParams] = useSearchParams(); return ( <div> <p> You searched for <i>{searchParams.get("q")}</i> </p> <FakeSearchResults /> </div> ); }

    仓库实现位于 lib/dom/lib.tsx#L2383。注意两条实战纪律:其一,searchParams.get("q")可能返回null(查询串里没有该键或没带=),必须处理缺失;其二,如果只是追加/替换某个键,应当在原有searchParams上拷贝修改,避免把用户可能并存的排序、过滤参数一起冲掉。

    Location Object

    React Router 会构建一个自定义的location对象,可通过useLocation读取,其上带有 pathname、search、hash、key 等有用信息:

    function useAnalytics() { let location = useLocation(); useEffect(() => { sendFakeAnalytics(location.pathname); }, [location]); } function useScrollRestoration() { let location = useLocation(); useEffect(() => { fakeRestoreScroll(location.key); }, [location]); }

    location.key在每次导航时都会变化,因此常被用于"按导航触发副作用"(如发送页面 PV、恢复滚动位置)的依赖项。由于这些值都是从 URL/历史状态解码而来,业务侧需要自行完成类型转换与边界校验。

    模式边界:Declarative 里没有这些 API

    Declarative Mode 的边界是明确的:它不提供Data/Framework 相关的整套 API,包括:

    • loader
    • action
    • <Form>
    • useFetcher
    • useNavigation
    • route module exports(如Route.ComponentProps、+types等框架级产物)
    • 生成的./+types路由类型

    docs/start/modes.md 中有一张"API × Mode 可用性"总表。筛选出Declarative 列可用的能力如下(也是你在这个模式里能依赖的全部武器):

    APIFrameworkDataDeclarative
    Link✅✅✅
    NavLink✅✅✅
    Navigate✅✅✅
    Outlet✅✅✅
    Route✅✅✅
    Routes✅✅✅
    useBeforeUnload✅✅✅
    useHref✅✅✅
    useInRouterContext✅✅✅
    useLinkClickHandler✅✅✅
    useLocation✅✅✅
    useMatch✅✅✅
    useNavigate✅✅✅
    useNavigationType✅✅✅
    useOutlet✅✅✅
    useOutletContext✅✅✅
    useParams✅✅✅
    useResolvedPath✅✅✅
    useRoutes✅✅✅
    useSearchParams✅✅✅
    createPath / createSearchParams / generatePath✅✅✅
    matchPath / matchRoutes / parsePath / renderMatches / resolvePath✅✅✅

    (createRoutesFromElements只在 Data 列可用,是数据路由把 JSX<Route>转换为路由对象时的桥梁。)

    对应地,useNavigation、useLoaderData、useActionData、useRevalidator、useFetcher(s)、useMatches、Await、Form、useBlocker、ScrollRestoration等在这一列均为空——它们是升级到 Data Mode 之后才会解锁的。

    什么时候该升级到 Data / Framework Mode

    参考文档给出明确的建议:如果用户提出以下诉求,就应该推荐 Data Mode 或 Framework Mode(具体选哪个,取决于对方想要多少脚手架与约定):

    • 路由数据加载(route data loading);
    • 依赖数据库/后端 API 的数据;
    • CRUD;
    • 表单变更(mutations);
    • 需要从提交回传的校验错误(validation returned from submissions);
    • 重新校验(revalidation);
    • pending UI;
    • optimistic UI;
    • fetchers。

    迁移属于架构决策,应先征求用户意见再动手,除非对方已经主动提出迁移要求——本模式边界正是为了避免"无意识地给声明式应用强行嫁接 data 能力"。

    文档使用提示:注意[MODES: ...]标记

    当前仓库的文档体系是多模式共存的:一份*.md可能同时适用于多种模式。因此在查阅 docs 时,请先确认文件头部的[MODES: framework, data, declarative]标记:只有包含declarative的文档才适用于声明式应用。最稳妥的阅读路径是:

    docs/start/modes.md # 先决定模式 docs/start/declarative/index.md # 声明式入口 docs/start/declarative/installation.md docs/start/declarative/routing.md docs/start/declarative/navigating.md docs/start/declarative/url-values.md

    概念性背景则参考 docs/explanation 下的对应主题。按这条路径阅读,既能保证你获得的知识与当前代码版本一致,也能避免把 Data/Framework 专有能力误用到声明式应用中。

    小结

    Declarative Mode 是 React Router 中"只做路由该做的事"的极简模式:以<BrowserRouter>/<HashRouter>/<MemoryRouter>为根(dom/lib.tsx),以<Routes>/<Route>描述 URL 与组件的映射(components.tsx),通过<Link>/<NavLink>/useNavigate完成导航,用useParams/useSearchParams/useLocation消费 URL 值(hooks.tsx、dom/lib.tsx)。它没有 loader/action/fetcher,也没有 contenteditable="false">【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询