关于 web-routing-react-router
web-routing-react-router 是面向 React 开发者的 React Router v7 Data Mode 指南,适用于构建路由驱动的应用。内容聚焦 loaders、actions、fetchers、导航状态和错误处理,并指出版本特定的使用注意事项。
主要功能
- 01Data Mode 路由器设置模式
- 02Loader 与 action 数据流程
- 03基于 fetcher 的变更操作
- 04路由错误与待处理状态
- 05嵌套路由布局与代码拆分
适用场景
- 01在组件渲染前加载路由数据
- 02使用 action 处理表单和变更
- 03不改变 URL 地更新数据
- 04添加路由级错误和加载指示
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-routing-react-router安装后在 Claude Code 中输入/web-routing-react-router确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.yamlreference.mdexamples/core.mddata-loading.mderror-handling.mdlayouts.mdnavigation.md详细介绍
React Router 模式
快速指南: 此技能介绍 React Router v7 的数据模式(Data Mode)——使用
createBrowserRouter,搭配 loaders、actions、fetchers 和待处理状态,无需完整框架。有 4 项 v7 事实会改变解答:所有导出都来自"react-router",而仅用于 DOM 的导出(包括RouterProvider)也可从"react-router/dom"获取;react-router-dom在 v7 中仍作为已弃用的重新导出包保留,并会在 v8 中删除;json()和defer()已移除,因此 loaders 会返回普通对象;表单方法值需使用大写("POST");如果 action 出错,loaders 会跳过重新验证,除非通过shouldRevalidate重新启用。
详细资源:
- examples/core.md — 路由器设置、根布局、根错误边界,以及 2 个 v7 迁移陷阱
- examples/data-loading.md — loaders、actions、
<Form>、useFetcher、乐观 UI、shouldRevalidate、延迟数据 - examples/navigation.md — 将
Link、NavLink、useNavigate、redirect、useSearchParams与 loaders 配合使用 - examples/error-handling.md — 错误冒泡、抛出的响应、待处理 UI、
route.lazy - examples/layouts.md —
Outlet、useOutletContext、无路径的身份验证布局、useBlocker - reference.md — 路由选项、钩子和组件表、
createBrowserRouter选项
适用路径
React Router 提供 3 种模式,但只有其中一种包含数据 API。
- 数据模式(Data Mode)——
createBrowserRouter+<RouterProvider>。Loaders、actions、fetchers 和errorElement均可使用。本技能介绍的就是这种模式;请从 examples/core.md 开始。 - 声明式模式(Declarative Mode)——
<BrowserRouter>+<Routes>。仅支持 URL 匹配和<Link>。此模式下的loaderprop 会被静默忽略,因此仅在没有数据需要加载,且本技能中的模式都不适用时才选择此模式。 - 框架模式(Framework Mode)——通过路由器自带的打包插件实现基于文件的路由、SSR 和流式传输。这是一个独立的接口,有自己的约定,本文不作介绍。
<critical_requirements>
编写 React Router 代码前
所有内容都从 "react-router" 导入,RouterProvider 则从 "react-router/dom" 导入——这个版本接入了 react-dom 的 flushSync。react-router-dom 在 v7 中仍会作为重新导出包安装,因此过时的导入不会导致构建失败,也没有任何提示;v8 会删除这个包。
**只要涉及 loader、action 或 fetcher,就使用 createBrowserRouter + <RouterProvider>。**这些都是 Data Mode 功能;在 <BrowserRouter> 下,这些属性会被接受但忽略,也不会有错误提示。
loader 返回普通对象,或返回你自行创建的 Response。json() 和 defer() 已在 v7 中移除。
**使用 throw redirect(),而不是 return redirect()。**抛出会展开整个调用栈,这样共享的 requireAuth() 辅助函数才能停止调用它的 loader。
**为根路由设置 errorElement 或 ErrorBoundary。**它是路由树中最后一道捕获关卡;如果没有设置,单个 loader 失败就会用路由器内置的错误详情页替换整个应用。
</critical_requirements>
自动检测: createBrowserRouter、RouterProvider、useLoaderData、useActionData、useNavigation、useSearchParams、useFetcher、useRouteError、isRouteErrorResponse、useOutletContext、useRevalidator、useBlocker、Outlet、NavLink、errorElement、shouldRevalidate、route.lazy、HydrateFallback
适用于:
- 包含数据加载、表单操作和待处理状态的路由树
- 带有持久共享 UI 的嵌套路由布局
- 路由级错误边界和未找到处理
- 将 URL 查询参数作为应用状态
- 不触发导航的变更——行内表单、开关、自动保存
- 路由级代码拆分
其他部分处理:
- 跨路由缓存和去重服务器数据——loader 会在每次导航时获取数据,而由负责数据获取的部分决定在导航之间缓存什么。
- 与 URL 无关的客户端状态——本指南只处理与 URL 相关的状态。
- 设置活动链接和待处理链接的样式——
NavLink会将其状态传给className或style函数,而这些函数返回什么不在它的职责范围内。 - 服务器渲染、HTML 流式传输和基于文件的路由生成。
React Router v7 将路由器视为数据层,而不是 URL 匹配器。路由声明要加载什么(loader)、接受什么变更(action),以及如何捕获失败(errorElement)——所有这些都发生在组件渲染之前。这样一来,编排逻辑就从组件中移出,也消除了挂载时获取数据造成的瀑布流。
- **路由负责自己的数据。**组件接收数据,而不负责获取数据。
- **URL 是事实来源。**路径参数、查询参数和导航状态都保存在其中。
- **错误会向上冒泡。**错误会传到最近的
errorElement,让父级布局继续显示。 - **重新验证是自动的。**action 成功后会重新运行所有活动 loader,因此无需手动使缓存失效。
<decision_framework>
逻辑放在哪里
What does this code do?
+-- Fetch data the component needs? -> loader (parallel with sibling loaders)
+-- Handle a form submission? -> action (reads FormData, returns errors or redirects)
+-- Mutate without changing the URL? -> useFetcher (independent state, runs concurrently)
+-- Gate access to a subtree? -> pathless layout route whose loader throws redirect()
如何导航
Where are you?
+-- In JSX, on something clickable? -> <Link> or <NavLink>
+-- In a handler, after a side effect? -> useNavigate()
+-- In a loader or action? -> throw redirect()
+-- Outside the React tree entirely? -> router.navigate() on the router object
最后一种是永远不会渲染模块的唯一退出方式——例如,在 401 响应时重定向的 fetch 封装器。所有这些 Hook 都需要在组件中使用。
在哪里捕获错误
在根路由上设置 errorElement,确保一定能捕获错误;在失败需要专属 UI 的路由上也设置它——文章缺失和仪表盘加载失败的展示方式就不一样。从 loader 抛出 Response(throw new Response("Not Found", { status: 404 })),可将失败作为 HTTP 错误处理,并通过 isRouteErrorResponse 缩小类型范围。
</decision_framework>
核心模式
模式 1:Data Mode 设置
路由是对象,应在 React 之外定义,这样路由器就能在任何内容挂载之前运行 loader。
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
errorElement: <RootError />,
children: [
{ index: true, element: <HomePage /> },
{ path: "posts", element: <PostsPage />, loader: postsLoader },
],
},
]);
完整代码:examples/core.md
模式 2:加载器和操作
加载器在渲染前运行;操作处理提交。两者都会收到 { request, params },其中 request 是标准的 Web Request。同级加载器会并行运行,父级先于子级。
export async function postsLoader({ request }: LoaderFunctionArgs) {
const response = await fetch("/api/posts");
if (!response.ok) throw new Response("Failed to load", { status: 500 });
return { posts: await response.json() };
}
export async function createPostAction({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const post = await createPost({ title: formData.get("title") });
throw redirect(`/posts/${post.id}`);
}
模式 3:使用 errorElement 处理错误边界
errorElement 会捕获路由的加载器、操作和组件中的错误。isRouteErrorResponse 可区分抛出的 Response 和意外异常。
function RouteError() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return <p role="alert">{error.status}: {error.statusText}</p>;
}
return <p role="alert">{error instanceof Error ? error.message : "Unknown error"}</p>;
}
完整代码:examples/error-handling.md
模式 4:使用 Outlet 创建嵌套布局
父级路由会在 <Outlet /> 所在位置渲染匹配的子级路由。子级路由间导航时布局会保留,因此侧边栏及其状态也会保留。
function DashboardLayout() {
return (
<div>
<nav><NavLink to="/dashboard" end>Overview</NavLink></nav>
<main><Outlet /></main>
</div>
);
}
完整代码:examples/layouts.md
模式 5:使用 useNavigation 获取导航状态
navigation.state 的值为 "idle"、"loading"(加载器正在运行)或 "submitting"(操作正在运行)。根布局中的一个指示器即可覆盖整个应用。
function GlobalSpinner() {
const navigation = useNavigation();
if (navigation.state === "idle") return null;
return <div role="progressbar" aria-busy="true" />;
}
完整代码:examples/error-handling.md
模式 6:使用 useFetcher 执行不触发导航的变更
fetcher.Form 会提交到操作,但不会更改 URL。Fetcher 各自拥有独立的 state 和 data,可以同时有多个请求在执行;每个请求完成后,仍会重新验证活动中的加载器。
function DeleteButton({ postId }: { postId: string }) {
const fetcher = useFetcher();
return (
<fetcher.Form method="POST" action={`/posts/${postId}/delete`}>
<button type="submit" disabled={fetcher.state !== "idle"}>Delete</button>
</fetcher.Form>
);
}
模式 7:使用 route.lazy 进行代码拆分
route.lazy 会延迟加载路由模块,直到发生导航。函数形式会打包为一个代码块;v7.5+ 的对象形式会按属性拆分,因此加载器和组件可以并行下载。
{ path: "admin", lazy: () => import("./pages/admin") }
{
path: "admin",
lazy: {
loader: async () => (await import("./pages/admin.loader")).loader,
Component: async () => (await import("./pages/admin.component")).AdminPage,
},
}
完整代码:examples/error-handling.md
<red_flags>
注意事项
会在运行时出错:
- 从
"react-router-dom"导入——该包在 v7 中已弃用,并将在 v8 中删除,而且如今仍能解析,因此不会有任何提示——请从"react-router"导入;对于RouterProvider,请从"react-router/dom"导入。 - 调用
json()或defer()——它们已在 v7 中移除——请返回普通对象,或返回一个代表延迟部分的 Promise,并使用<Await>读取。 - 在
<BrowserRouter>下的<Route>中设置loader或action——它们会被接受并忽略,且不会发出警告——请将路由树改为使用createBrowserRouter。 - 整棵路由树中都没有
errorElement——一次加载器错误就会让应用被路由器默认的错误信息替换——请在根路由上添加一个。 - 在共享辅助函数中使用
return redirect()——调用方会继续执行,并将Response作为返回值处理——应改为throw。 - 布局没有渲染
<Outlet />——匹配的子级路由不会渲染到任何位置,且不会发出提示。 - 在没有加载器的路由所对应的组件中调用
useLoaderData()——它会返回undefined,且问题发生的位置可能离原因很远。 - 使用
navigation.formMethod === "post"——v7 会将表单方法转换为大写——请与"POST"比较。
容易出乎意料的行为:
- 操作成功后,所有活动中的加载器都会重新验证,而不仅是执行操作的路由。操作发生_错误_后,没有任何加载器会重新验证,除非
shouldRevalidate选择重新启用。 useFetcher不会改变useNavigation的状态;两者相互独立。useSearchParams的 setter 不会像setState那样排队处理——在同一个 tick 中调用两次不会基于前一次调用的结果,因此请使用回调形式。route.lazy不能提供path、index、children或id;这些仍须放在静态配置中。params的值始终是字符串——请在加载器中解析,而不是在组件中解析。- 操作返回数据而不是重定向,会导致无法访问该结果,因此刷新后会显示变更前的 UI。
navigate(-1)假设历史记录中存在上一条记录,而通过深层链接进入的访客可能没有这样的记录。
</red_flags>