
React Router 6.x 备忘清单数据路由与组件路由的完整实战指南【免费下载链接】reference面向开发者的技术速查清单Cheat Sheets集合整理常见技术、工具与开发流程帮助快速查阅关键信息提高开发效率。项目地址: https://gitcode.com/GitHub_Trending/referen/reference本篇指南以当前仓库中的 React Router 备忘清单 为骨架系统讲解 React Router 6.xv6.4 数据路由时代的安装接入、路由配置、loader/action 数据流、嵌套路由、表单提交、搜索参数同步以及createBrowserRouter/createHashRouter/createMemoryRouter等数据路由与BrowserRouter/StaticRouter等组件路由的选型与用法。读完本文你将能够独立完成一个基于数据 API 的完整单页应用如联系人管理 CRUD并理解各路由方案的适用场景与底层差异。该清单是本仓库面向中文开发者的前端速查板块见 README.md中的一部分可直接对照查阅。环境准备与安装使用使用 Vite 官方脚手架创建一个 React 项目并安装 React Router 及其配套的本地数据层依赖$ npm create vitelatest myApp --\ --template react # 按照提示操作 $ cd myApp $ npm install react-router-dom \ localforage \ match-sorter \ sort-by $ npm run dev其中react-router-dom是浏览器端路由核心包localforage用于浏览器本地持久化存储模拟后端数据库match-sorter提供模糊搜索排序sort-by用于对列表数据排序。后三者通常在官方教程的 contacts 示例中配合使用以在前端完整模拟一个数据访问层。添加路由器createBrowserRouter 与 RouterProviderv6.4 起推荐使用数据路由 API。createBrowserRouter接收一个路由对象数组并返回路由器实例再通过RouterProvider将路由接入 React 渲染树import React from react; import ReactDOM from react-dom/client; import { createBrowserRouter, RouterProvider, } from react-router-dom; const router createBrowserRouter([ { path: /, element: divHello world!/div, }, ]); const rootdocument.getElementById(root); ReactDOM.createRoot(root).render( React.StrictMode RouterProvider router{router} / /React.StrictMode );与传统的BrowserRouter声明式写法不同这里路由以对象形式集中定义便于后续挂载loader、action、errorElement等数据 API 配置是掌握 React Router 6.x 的关键入口。根路由与未找到错误处理根路由先编写根组件Root再在路由配置中将其挂载到/路径import Root from ./routes/root; const router createBrowserRouter([ { path: /, element: Root /, }, ]);处理未找到错误为路由配置errorElement当匹配路由失败、loader/action 抛错或渲染出错时该元素会作为错误边界渲染import ErrorPage from ./error-page; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, }, ]);errorElement可以在任意层级配置错误会向上冒泡到最近的一层错误边界这是实现404 页面与异常兜底的核心机制。contacts 用户界面与嵌套路由contacts 路由为联系人详情页定义带动态参数的子路由import Contact from ./routes/contact; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, }, { path: contacts/:contactId, element: Contact /, }, ]);嵌套路由将contacts/:contactId移入根路由的children形成层级关系// src/main.jsx const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, children: [ { path: contacts/:contactId, element: Contact /, }, ], }, ]);父路由组件通过Outlet /渲染匹配到的子路由元素// src/routes/root.jsx import { Outlet } from react-router-dom; export default function Root() { return ( {/* 所有其他元素 */} div iddetail Outlet / /div / ); }嵌套路由让 URL、组件层级与布局结构一一对应父布局侧边栏、导航、页头只需编写一次。客户端路由用Link替代a标签实现无刷新跳转的客户端路由import { Outlet, Link } from react-router-dom; export default function Root() { return ( ul li Link to{contacts/1} Your Name /Link /li li Link to{contacts/2} Your Friend /Link /li /ul ); }Link拦截默认跳转行为通过 History API 更新 URL 并仅重新渲染必要部分避免整页刷新。数据 API用 loader 与 action 驱动 CRUD创建联系人将form替换为Form由 React Router 接管提交在Root组件中导出action通过createContact()创建新联系人import { Outlet, Link, useLoaderData, Form, } from react-router-dom; import { getContacts, createContact } from ../contacts; export async function action() { const contact await createContact(); return { contact }; } export default function Root() { const { contacts } useLoaderData(); return ( div idsidebar h1React Router Contacts/h1 Form methodpost button typesubmitNew/button /Form /div ); }再将action挂到路由配置上import Root, { loader as rootLoader, action as rootAction, } from ./routes/root; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, loader: rootLoader, action: rootAction, children: [ { path: contacts/:contactId, element: Contact /, }, ], }, ]);加载程序中的 URL 参数动态段路由路径中的:contactId冒号段即动态段匹配任意 URL 片段并通过params对象注入 loader[ { path: contacts/:contactId, element: Contact /, }, ];在Contact组件模块中导出loader用params.contactId加载数据组件内通过useLoaderData()消费import { useLoaderData } from react-router-dom; import { getContact } from ../contacts; export async function loader({ params }) { return getContact(params.contactId); } export default function Contact() { const contact useLoaderData(); // existing code }将loader挂到对应路由import Contact, { loader as contactLoader, } from ./routes/contact; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, loader: rootLoader, action: rootAction, children: [ { path: contacts/:contactId, element: Contact /, loader: contactLoader, }, ], }, ]);loader 在导航发生时于渲染之前执行数据就绪后组件再挂载天然消除了手动请求状态的样板代码。更新数据编辑表单编辑页面同样使用Form methodpost并用useLoaderData()预填当前值import { Form, useLoaderData } from react-router-dom; export default function EditContact() { const contact useLoaderData(); return ( Form methodpost idcontact-form label spanTwitter/span input typetext nametwitter placeholderjack defaultValue{contact.twitter} / /label label spanNotes/span textarea namenotes defaultValue{contact.notes} rows{6} / /label p button typesubmit保存/button button typebutton取消/button /p /Form ); }添加编辑路由并复用同一个contactLoaderimport EditContact from ./routes/edit; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, loader: rootLoader, action: rootAction, children: [ { path: contacts/:contactId, element: Contact /, loader: contactLoader, }, { path: contacts/:contactId/edit, element: EditContact /, loader: contactLoader, }, ], }, ]);使用 FormData 更新联系人在编辑模块中导出action从request.formData()解析表单字段调用updateContact后通过redirect跳回详情页import { Form, useLoaderData, redirect, } from react-router-dom; import { updateContact } from ../contacts; export async function action({ request, params }) { const formData await request.formData(); const updates Object.fromEntries(formData); await updateContact(params.contactId, updates); return redirect(/contacts/${params.contactId}); }Object.fromEntries(formData)将表单字段一次性转换为普通对象是解析Form提交的标准写法。把action连接到编辑路由import EditContact, { action as editAction, } from ./routes/edit; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, loader: rootLoader, action: rootAction, children: [ { path: contacts/:contactId, element: Contact /, loader: contactLoader, }, { path: contacts/:contactId/edit, element: EditContact /, loader: contactLoader, action: editAction, }, ], }, ]);删除记录删除采用methodpost 相对actiondestroy的表单并在提交前用confirm二次确认Form methodpost actiondestroy onSubmit{(event) { if (!confirm(请确认您要删除此记录)) { event.preventDefault(); } }} button typesubmit删除/button /Form添加销毁动作删除后重定向回首页import {redirect} from react-router-dom; import {deleteContact} from ../contacts; export async function action({ params }) { await deleteContact(params.contactId); return redirect(/); }将destroy路由添加到配置中import { action as destroyAction } from ./routes/destroy; const router createBrowserRouter([ { path: /, children: [ { path: contacts/:contactId/destroy, action: destroyAction, }, ], }, ]);上下文错误action 中抛出的错误会传递给最近一层的errorElement实现局部错误展示export async function action({ params }) { throw new Error(oh dang!); await deleteContact(params.contactId); return redirect(/); }为destroy路由单独配置上下文错误消息[ { path: contacts/:contactId/destroy, action: destroyAction, errorElement: div哎呀有一个错误/div, }, ];这是与根级errorElement互补的细粒度错误处理方式只有该路由的 action 失败时才展示此错误块不影响页面其他区域。首页路由Index Route当父路由路径精确匹配、无子路由命中时index: true的子路由作为默认内容渲染import Index from ./routes/index; const router createBrowserRouter([ { path: /, element: Root /, errorElement: ErrorPage /, loader: rootLoader, action: rootAction, children: [ { index: true, element: Index / }, ], }, ]);Index Route 没有自己的路径段非常适合渲染空状态默认页。取消按钮使用useNavigate()编程式导航navigate(-1)返回上一页import { Form, useLoaderData, redirect, useNavigate, } from react-router-dom; export default function Edit() { const contact useLoaderData(); const navigate useNavigate(); return ( Form methodpost idcontact-form div button typesubmit保存/button button typebutton onClick{() { navigate(-1); }} 取消 /button /div /Form ); }导航状态与界面反馈活动链接样式NavLink提供isActive/isPending状态可据此为当前导航项与待定导航项附加不同样式import { NavLink, } from react-router-dom; NavLink to{contacts/${contact.id}} className{({ isActive, isPending }) isActive ? active : isPending ? pending : } {/* other code */} /NavLinkisPending表示该链接对应的路由正处于加载中常配合 CSS 实现导航已点击但数据未就绪的视觉反馈。全局待定用户界面useNavigation()暴露全局导航状态navigation.state loading时可为详情容器添加 loading 类名import { useNavigation, } from react-router-dom; export default function Root() { const { contacts } useLoaderData(); const navigation useNavigation(); return ( div iddetail className{ navigation.state loading ? loading : } Outlet / /div ); }相比每个组件各自维护加载态这种全局 UI 反馈写法更简洁配合 CSS 动画即可实现优雅的过渡效果。搜索功能与 URL 状态同步使用客户端路由获取提交将原生form改为Form搜索框输入不再触发整页刷新Form idsearch-form rolesearch input idq aria-labelSearch contacts placeholderSearch typesearch nameq / /Form在 loader 中通过request.url解析URLSearchParams实现基于查询参数的过滤export async function loader({ request }) { const url new URL(request.url); const q url.searchParams.get(q); const contacts await getContacts(q); return { contacts }; }将 URL 同步到表单状态将查询参数q随 loader 一起返回并用defaultValue回填输入框保证刷新或分享 URL 后搜索框与 URL 一致export async function loader({ request }) { const url new URL(request.url); const q url.searchParams.get(q); const contacts await getContacts(q); return { contacts, q }; } export default function Root() { const { contacts, q } useLoaderData(); const navigation useNavigation(); return ( Form idsearch-form rolesearch input idq aria-labelSearch contacts placeholderSearch typesearch nameq defaultValue{q} / {/* existing code */} /Form ); }将输入值与 URL 搜索参数同步当用户点击浏览器的前进/后退按钮改变 URL 时用useEffect监听q的变化并同步输入框的值import { useEffect } from react; export default function Root() { const { contacts, q } useLoaderData(); const navigation useNavigation(); useEffect(() { document.getElementById(q).value q; }, [q]); }defaultValue负责首次渲染useEffect负责 URL 变化后的回写两者配合实现双向同步。提交变更 Forms借助useSubmit()在onChange时直接提交表单实现输入即搜索的即时过滤体验import { useSubmit, } from react-router-dom; export default function Root() { const { contacts, q } useLoaderData(); const navigation useNavigation(); const submit useSubmit(); return ( Form idsearch-form rolesearch input idq aria-labelSearch contacts placeholderSearch typesearch nameq defaultValue{q} onChange{(event) { submit(event.currentTarget.form); }} / {/* existing code */} /Form ); }submit接收表单元素或 FormData可指定method、action等选项将任意交互输入、点击、拖拽转成一次路由级提交。Routers数据路由与组件路由的选型挑选路由器v6.4 引入了支持新数据 API 的路由器路由器说明createBrowserRouter基于 History API 的数据路由推荐用于浏览器端 SPAcreateMemoryRouter内存路由位置存储在内存数组中适合测试createHashRouter基于 URL hash 的数据路由适合无法配置服务器的场景以下组件式路由器不支持数据dataAPI无 loader/action路由器说明BrowserRouter组件式浏览器路由MemoryRouter组件式内存路由HashRouter组件式 hash 路由NativeRouterReact Native 环境路由StaticRouter服务端渲染SSR路由从源码结构看数据路由以create开头返回路由器实例并由RouterProvider消费而组件路由直接包裹应用 JSX需要 loader/action 等数据能力时必须选用前者。路由示例createRoutesFromElements从 v6.4 之前的 JSXRoute写法升级时可用createRoutesFromElements将Route元素直接转换为路由对象无需手工改写import { createBrowserRouter, createRoutesFromElements, Route, RouterProvider, } from react-router-dom; const router createBrowserRouter( createRoutesFromElements( Route path/ element{Root /} Route pathdashboard element{Dashboard /} / {/* ... etc. */} /Route ) ); ReactDOM.createRoot(document.getElementById(root)).render( React.StrictMode RouterProvider router{router} / /React.StrictMode );数据路由详解createBrowserRouter对象式配置路由逐层挂载 loader 与子路由import * as React from react; import * as ReactDOM from react-dom; import { createBrowserRouter, RouterProvider, } from react-router-dom; import Root, {rootLoader} from ./root; import Team, {teamLoader} from ./team; const router createBrowserRouter([ { path: /, element: Root /, loader: rootLoader, children: [ { path: team, element: Team /, loader: teamLoader, }, ], }, ]); const rootdocument.getElementById(root); ReactDOM.createRoot(root).render( RouterProvider router{router} / );Type Declaration类型签名function createBrowserRouter( routes: RouteObject[], opts?: { basename?: string; window?: Window; } ): RemixRouter;routescreateBrowserRouter([ { path: /, element: Root /, loader: rootLoader, children: [ { path: events/:id, element: Event /, loader: eventLoader, }, ], }, ]);basename当应用部署在域名子目录而非根路径时使用。注意末尾斜杠会影响生成的链接createBrowserRouter(routes, { basename: /app, }); createBrowserRouter(routes, { basename: /app, }); Link to/ /; // results in a href/app / createBrowserRouter(routes, { basename: /app/, }); Link to/ /; // results in a href/app/ /即basename为/app时Link to/ /生成a href/app /为/app/时生成a href/app/ /。createHashRouter与createBrowserRouter用法一致但 URL 使用#片段无需服务端回退配置import * as React from react; import * as ReactDOM from react-dom; import { createHashRouter, RouterProvider, } from react-router-dom; import Root, { rootLoader } from ./root; import Team, { teamLoader } from ./team; const router createHashRouter([ { path: /, element: Root /, loader: rootLoader, children: [ { path: team, element: Team /, loader: teamLoader, }, ], }, ]); const rootdocument.getElementById(root); ReactDOM.createRoot(root).render( RouterProvider router{router} / );适用于静态托管、无法将所有路径重写到index.html的环境。createMemoryRouter位置保存在内存数组中不依赖浏览器历史栈是单元测试的理想选择。结合testing-library/react的完整测试示例import { RouterProvider, createMemoryRouter, } from react-router-dom; import * as React from react; import { render, waitFor, screen, } from testing-library/react; import testing-library/jest-dom; import CalendarEvent from ./event; test(event route, async () { const FAKE_EVENT { name: 测试事件 }; const routes [ { path: /events/:id, element: CalendarEvent /, loader: () FAKE_EVENT, }, ]; const routercreateMemoryRouter(routes,{ initialEntries: [/, /events/123], initialIndex: 1, }); render( RouterProvider router{router} / ); await waitFor( () screen.getByRole(heading) ); expect(screen.getByRole(heading)) .toHaveTextContent( FAKE_EVENT.name ); });initialEntries初始历史栈条目数组createMemoryRouter(routes, { initialEntries: [/, /events/123], });initialIndex初始激活的条目索引从 0 开始createMemoryRouter(routes, { initialEntries: [/, /events/123], initialIndex: 1, // start at /events/123 });RouterProviderRouterProvider将路由器实例接入 React 渲染树可通过fallbackElement提供加载占位import { createBrowserRouter, RouterProvider, } from react-router-dom; const router createBrowserRouter([ { path: /, element: Root /, children: [ { path: dashboard, element: Dashboard /, }, { path: about, element: About /, }, ], }, ]); const rootdocument.getElementById(root); ReactDOM.createRoot(root).render( RouterProvider router{router} fallbackElement{BigSpinner /} / );fallbackElement若未做服务端渲染数据路由在挂载时会启动所有匹配路由的 loader。在此期间可提供fallbackElement提示用户应用正在运行RouterProvider router{router} fallbackElement{SpinnerOfDoom /} /Router 组件详解以下组件式路由器适用于不需要数据 API 的轻量场景。BrowserRouterBrowserRouter使用干净的 URL 将当前位置存储在浏览器地址栏并基于浏览器内置历史堆栈导航import * as React from react; import * as ReactDOM from react-dom; import { BrowserRouter } from react-router-dom; const rootdocument.getElementById(root); ReactDOM.createRoot(root).render( HashRouter {/* 你的应用程序的其余部分在这里 */} /HashRouter, );HashRouterHashRouter用于 Web 浏览器中因某些原因不应或不能将 URL 发送到服务器的场景如静态文件托管、无法配置服务器回退时import * as React from react; import * as ReactDOM from react-dom; import { HashRouter } from react-router-dom; const rootdocument.getElementById(root); ReactDOM.createRoot(root).render( HashRouter {/* 你的应用程序的其余部分在这里 */} /HashRouter, );NativeRouterNativeRouter是在 React Native 应用中运行 React Router 的推荐接口来自react-router-native包import * as React from react; import { NativeRouter } from react-router-native; function App() { return ( NativeRouter {/* 你的应用程序的其余部分在这里 */} /NativeRouter ); }MemoryRouterMemoryRouter在内部将位置存储于数组中不像BrowserHistory/HashHistory那样依赖外部历史栈因此非常适合需要完全控制历史栈的场景例如测试快照渲染import * as React from react; import { create } from react-test-renderer; import { MemoryRouter, Routes, Route, } from react-router-dom; describe(My app, () { it(renders correctly, () { let renderer create( MemoryRouter initialEntries{[/users/mjackson]} Routes Route pathusers element{Users /} Route path:id element{UserProfile /} / /Route /Routes /MemoryRouter ); expect(renderer.toJSON()).toMatchSnapshot(); }); });RouterRouter是所有路由器组件如BrowserRouter和StaticRouter共享的低级接口。就 React 而言Router是一个上下文提供者向应用其余部分提供路由信息。日常开发通常使用其上层封装组件而非直接使用Router。StaticRouterStaticRouter用于服务端渲染在 Node 服务中基于req.url渲染出对应 HTML再返回给客户端import * as React from react; import * as ReactDOMServer from react-dom/server; import { StaticRouter } from react-router-dom/server; import http from http; function requestHandler(req, res) { let html ReactDOMServer.renderToString( StaticRouter location{req.url} {/* 你的应用程序的其余部分在这里 */} /StaticRouter ); res.write(html); res.end(); } http.createServer(requestHandler) .listen(3000);注意此处从react-router-dom/server子路径导入这是 SSR 场景的标准用法。小结React Router 6.x 的核心脉络可概括为两条主线一是以createBrowserRouter/createHashRouter/createMemoryRouter为代表的数据路由体系配合loader、action、Form、useNavigation、useSubmit等 API 将数据获取、表单提交与导航状态统一纳入路由层二是以BrowserRouter、StaticRouter、NativeRouter等为代表的组件路由体系适合轻量或 SSR/RN 场景。实战选型时需要数据 API 的浏览器应用优先createBrowserRouter无法配置服务器回退则用createHashRouter单元测试用createMemoryRouterSSR 用StaticRouter。完整示例与全部代码片段可随时回查仓库中的 React Router 备忘清单其姊妹篇 React 备忘清单 也可作为配套参考。【免费下载链接】reference面向开发者的技术速查清单Cheat Sheets集合整理常见技术、工具与开发流程帮助快速查阅关键信息提高开发效率。项目地址: https://gitcode.com/GitHub_Trending/referen/reference创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考