React Router 路由速查表 - React Router v6 路由配置与导航大全
这张速查给在 React 应用里做客户端导航的开发者,用的是 React Router v6。内容涵盖 Routes/Route 的声明式路由树、用 Outlet 实现的嵌套布局、Link/NavLink 与 useNavigate 编程式导航、动态参数 :id 与查询字符串的读写、受保护页面的登录守卫,以及 v6.4 新增的 loader/action 数据加载与提交 API。每条都对应一个具体缺口,比如渲染 404、保持导航高亮或登录后重定向。读完你应能声明一棵路由树、在页面间跳转、处理 :id 与 ?query,并给需要登录的页面加守卫。
典型使用场景
面向需要在单页应用里管理 URL 状态的前端开发者:多页面切换、登录态保护、可分享的带参链接、以及「刷新后回到同一屏」。React Router 的价值是让 URL 成为唯一真相来源——筛选条件放 search params、当前实体放路径参数 :id、跨页回跳放 navigate 的 state。本表覆盖 v6 的 Routes / Route / Link / NavLink / Navigate、useParams / useSearchParams / useLocation / useMatch、嵌套路由与 Outlet、懒加载与 Suspense,以及 v6.4 起的数据加载(loader / action / errorElement)。
路由配置 5
<BrowserRouter><App /></BrowserRouter><Routes><Route path="/" element={<Home />} /></Routes><Route path="/users/:id" element={<User />} /><Route path="*" element={<NotFound />} /><Route path="/" element={<Layout />}>...</Route>导航 5
<Link to="/about">About</Link><NavLink to="/users" className={({ isActive }) => isActive ? "active" : ""}><Navigate to="/login" replace />const navigate = useNavigate()navigate("/dashboard", { replace: true, state: { from: "/" } })路由参数 5
<Route path="/users/:id" element={<User />} />const { id } = useParams()<Route path="/files/*" element={<Files />} />const splat = useParams()["*"]navigate(-1) / navigate(1)查询参数 5
const [params, setParams] = useSearchParams()params.get("q")setParams({ q: "keyword", page: 1 })<Link to="/search?q=react">Search</Link>params.toString()嵌套与布局 5
<Route path="/dashboard" element={<Dashboard />}><Route index element={<Overview />} /><Route path="settings" element={<Settings />} /><Outlet /><Outlet context={{ user }} />路由守卫与数据 5
function ProtectedRoute({ children }) { return isAuth ? children : <Navigate to="/login" /> }<Route element={<ProtectedRoute><Outlet /></ProtectedRoute>}><Route loader={async ({ params }) => fetchUser(params.id)} /><Route action={async ({ request }) => submitForm(request)} /><Route errorElement={<ErrorBoundary />} />高级用法 6
const location = useLocation()location.pathname / location.search / location.hashconst match = useMatch("/users/:id")const Page = lazy(() => import("./Page"))<Route path="about" element={<Suspense><About /></Suspense>} /><ScrollRestoration />参数矩阵
| 参数 | 作用 | 示例 |
|---|---|---|
<Route path="/users/:id" element={<User />} /> | 动态路径参数:URL 对应段被捕获,用 useParams() 读取 | const { id } = useParams() |
<Route path="*" element={<NotFound />} /> | 兜底路由,匹配任何未命中的路径 | <Route path="*" element={<NotFound />} /> |
navigate(path, { replace, state }) | 编程式跳转,可替换历史记录并携带 state | navigate("/dashboard", { replace: true, state: { from: "/" } }) |
<NavLink className={({ isActive }) => ...}> | Link 的激活态版本,回调可拿到 isActive 做样式 | <NavLink to="/users" className={({ isActive }) => isActive ? "active" : ""}> |
useSearchParams() | 读写查询字符串,返回 [params, setParams] | const [params, setParams] = useSearchParams() |
<Outlet /> / <Outlet context={...} /> | 父组件渲染子路由,并可向子路由传 context | <Outlet context={{ user }} /> |
<Route index element={<Overview />} /> | 父路径下的默认子路由 | <Route index element={<Overview />} /> |
loader / action / errorElement | v6.4+ 路由级数据加载、提交与错误边界 | <Route loader={async ({ params }) => fetchUser(params.id)} /> |
path="/files/*" | splat 通配匹配多段路径,取值用 useParams()["*"] | const splat = useParams()["*"] |
易错点与避坑指南
现象嵌套路由里子页面一片空白,父布局正常显示但内容区没内容。
原因父组件里漏写了 <Outlet />,子路由元素没有渲染出口。
处置在父组件返回的布局中放 <Outlet />;需要给子路由传数据时用 <Outlet context={{ user }} />。
现象访问 /dashboard/settings 命中 404 或空白页。
原因子路由写成了以斜杠开头的绝对路径,或父路由没有包住子路由的 element。
处置父路由用 element={<Dashboard />} 包裹,子路由写相对路径:<Route path="settings" element={<Settings />} />。
现象点击 <Link> 走了整页刷新,控制台出现整页导航记录。
原因写成了原生 <a href="/about"> 而不是 <Link to="/about">。
处置统一改用 <Link to="/about">About</Link>,它渲染成 a 但由路由接管点击。
现象登录后跳回目标页丢失,或从子页面返回列表时状态被清空。
原因用 navigate(path) 追加了新的历史记录,或刷新后 state 已经不存在(state 只存在内存里)。
处置登录重定向用 replace 避免堆积历史:navigate("/dashboard", { replace: true, state: { from: "/" } });需要刷新后仍可恢复时把来源放进 search 参数而非 state。
现象useSearchParams 改一次筛选,地址栏后面堆了好长一串重复参数。
原因直接用 push 语义追加,或在 effect 里反复 setParams 造成循环。
处置用 setParams({ q: "keyword", page: 1 }) 一次性传入完整参数对象,并在依赖稳定后再调用。
现象404 页面永远不出现,未匹配路径渲染出空白。
原因<Route path="*" /> 被写在了前面,React Router 按声明顺序匹配,先匹配到的路由胜出。
处置把兜底路由 <Route path="*" element={<NotFound />} /> 放到 Routes 的最后一项。
排障路径
1确认当前路由匹配到的是哪一段
const match = useMatch("/users/:id")useMatch 返回匹配结果为 null 说明该模式没有命中路径,可用来验证通配与动态段的写法。
2拆开看当前 URL 的各部分
const location = useLocation()location.pathname / location.search / location.hash 分别对应路径、查询串与锚点,排查地址串问题时先打这里。
3处理需要登录才能进入的路由
<Route element={<ProtectedRoute><Outlet /></ProtectedRoute>}>未登录时 ProtectedRoute 返回 <Navigate to="/login" />,包裹形式便于一次保护多条路由。
4让路由级数据加载失败有兜底 UI
<Route errorElement={<ErrorBoundary />} />loader / action 抛错时渲染 errorElement,避免错误冒泡到整页白屏。
5为单个路由加懒加载
<Route path="about" element={<Suspense><About /></Suspense>} />配合 const Page = lazy(() => import("./Page")) 使用,避免一次性打进整个应用。
命令示例
声明式路由与兜底页
<BrowserRouter><App /></BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<User />} />
<Route path="*" element={<NotFound />} />
</Routes>path="*" 必须放在 Routes 末尾才能真正兜底;动态段 :id 由 useParams() 读取。
嵌套路由与 Outlet
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<Overview />} />
<Route path="settings" element={<Settings />} />
</Route>
// in Dashboard:
<Outlet />
<Outlet context={{ user }} />index 路由匹配 /dashboard 本身;父组件没有 Outlet 时子路由匹配成功却不会渲染出内容。
查询参数与路径参数的分工
const [params, setParams] = useSearchParams()
params.get("q")
setParams({ q: "keyword", page: 1 })
params.toString()
const { id } = useParams()
const splat = useParams()["*"]筛选条件放查询串、实体 id 放路径段;splat 路由 /files/* 的多段值从星号键读取。
编程式跳转与受保护路由
const navigate = useNavigate()
navigate("/dashboard", { replace: true, state: { from: "/" } })
navigate(-1)
navigate(1)
<Route element={<ProtectedRoute><Outlet /></ProtectedRoute>}>
<Route path="secret" element={<Secret />} />
</Route>replace 避免在历史里留下受保护页记录;state 携带来源路径,登录后可以跳回。
常见坑与注意事项
- 父路由组件里漏了 <Outlet />,子路由匹配成功也不会显示内容,父布局看起来一切正常。
- 子路由写绝对路径会脱离父级匹配关系,嵌套结构里一律用 path="settings" 这类相对路径。
- 导航一律用 <Link> 或 navigate 而不是普通 <a href>,后者会触发整页刷新并留下整页导航记录。
- 重定向登录页时务必加 replace,否则历史里残留受保护路由,登录后返回会被再次弹走。
- 未匹配路径要靠 <Route path="*" element={<NotFound />} /> 兜底,且它必须放在 Routes 的最后。
提示
- React Router v6 用 <Routes> 替代了 v5 的 <Switch>。
- useNavigate 替代了 v5 的 useHistory,navigate(-1) 后退,navigate(1) 前进。
- 嵌套路由需在父组件用 <Outlet /> 渲染子路由,index 路由匹配父路径本身。
- NavLink 的 className 可接收 { isActive } 回调实现激活样式。
- 动态参数 :id 用 useParams() 获取,查询参数 ?q= 用 useSearchParams() 读写。
常见问题
父路由能显示、子路由一片空白,通常缺了什么?
缺 <Outlet />。父组件渲染出布局后,必须在布局内部放一个 <Outlet /> 作为子路由的挂载点,否则子路由的 element 虽然匹配成功却没有渲染位置。父路由本身也要写成 <Route path="/dashboard" element={<Dashboard />}> 这种带子 Route 的形式,只写 path 与 element 却没有 Outlet 时同理。父层组件内容正常、控制台无报错,正是因为匹配成功但输出无处安放。
子路由该写相对路径还是绝对路径?
嵌套结构里用相对路径。父路由是 /dashboard 时,子路由写 path="settings" 拼成 /dashboard/settings;写绝对路径会脱离父级匹配关系。父路径下的默认页用 <Route index element={<Overview />} />,它匹配父路径本身。想在多个受保护页面外层统一加登录校验时,用 <Route element={<ProtectedRoute><Outlet /></ProtectedRoute>}> 包住这一组子路由,配合 navigate(-1) 可以在登录后返回原页面。
筛选条件该放路径还是查询字符串?
放查询字符串。用 const [params, setParams] = useSearchParams() 读写,setParams({ q: "keyword", page: 1 }) 一次更新会替换成新的查询串,params.get("q") 读单个值,params.toString() 可以拿整串去拼别处。这样筛选条件可分享、可收藏、刷新后保持;路径参数留给「当前实体是谁」这类身份信息,用 const { id } = useParams() 读。需要匹配多段路径时用 <Route path="/files/*" element={<Files />} />,段值从 useParams()["*"] 取。
怎么给某几个页面单独做懒加载?
用 lazy 把组件切成按需加载的块,再在包裹它的那个 Route 里放 Suspense 边界:const Page = lazy(() => import("./Page")),然后 <Route path="about" element={<Suspense><About /></Suspense>} />。边界要挂在具体路由上而不是应用根部,这样只有这一条路由的 chunk 在下载时显示兜底内容,其它页面照常切换。懒加载之后滚动位置不再由浏览器自动接管,可以配 <ScrollRestoration /> 恢复。
redirect 和在渲染前判断登录状态,选哪个更好?
渲染前判断,即让判断本身成为路由的一部分,这样受保护的子树根本不会被挂载。写法是 <Route element={<ProtectedRoute><Outlet /></ProtectedRoute>}>,未登录时 ProtectedRoute 返回 <Navigate to="/login" replace />。用 replace 是关键:否则浏览器历史里会留下一条本不该存在的受保护页记录,用户登录后返回又会被弹回登录页。另外可以在跳转时带上 state,登录成功后原路返回。
由 巧匠 维护
公开更新于 2026年7月21日,内容持续校对官方文档。