mcpField
W
web-routing-react-router

web-routing-react-router

github.com/agents-inc开发26108 次安装2 天前更新
代码仓库

关于 web-routing-react-router

web-routing-react-router 是面向 React 开发者的 React Router v7 Data Mode 指南,适用于构建路由驱动的应用。内容聚焦 loaders、actions、fetchers、导航状态和错误处理,并指出版本特定的使用注意事项。

主要功能

  1. 01Data Mode 路由器设置模式
  2. 02Loader 与 action 数据流程
  3. 03基于 fetcher 的变更操作
  4. 04路由错误与待处理状态
  5. 05嵌套路由布局与代码拆分

适用场景

  1. 01在组件渲染前加载路由数据
  2. 02使用 action 处理表单和变更
  3. 03不改变 URL 地更新数据
  4. 04添加路由级错误和加载指示

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill web-routing-react-router

安装后在 Claude Code 中输入/web-routing-react-router确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
core.md
data-loading.md
error-handling.md
layouts.md
navigation.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 重新启用。

详细资源:


适用路径

React Router 提供 3 种模式,但只有其中一种包含数据 API。

  • 数据模式(Data Mode)——createBrowserRouter + <RouterProvider>。Loaders、actions、fetchers 和 errorElement 均可使用。本技能介绍的就是这种模式;请从 examples/core.md 开始。
  • 声明式模式(Declarative Mode)——<BrowserRouter> + <Routes>。仅支持 URL 匹配和 <Link>。此模式下的 loader prop 会被静默忽略,因此仅在没有数据需要加载,且本技能中的模式都不适用时才选择此模式。
  • 框架模式(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}`);
}

完整代码:examples/data-loading.md


模式 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>
  );
}

完整代码:examples/data-loading.md


模式 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>

评论

支持 Markdown