React Router 如何编写 Resource Route 让路由直接返回 PDF、JSON 等响应而非组件?
【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router
如果你需要在 React Router 应用中让某个 URL 直接返回 PDF 文件、JSON 数据、图片甚至 webhook 响应,而不是渲染一段界面,官方文档把这个能力叫做Resource Routes:服务端渲染时,路由可以提供"资源"而不是组件(见 docs/how-to/resource-routes.md)。
Resource Routes 适用于framework 模式和 data 模式(文档标注[MODES: framework, data])。完成本场景的目标是:让一个路由不导出组件,由loader/action直接生成Response,并在应用的其他页面正确链接到它。
让路由变成 Resource Route:不导出组件即可
路由"按约定"成为 resource route 的条件只有一个:它的模块导出loader或action,但不导出 default 组件(docs/how-to/resource-routes.md)。
以"返回 PDF 报告"为例,先在路由配置中注册路由:
route("/reports/pdf/:id", "pdf-report.ts");然后编写路由模块,注意没有 default export,这就是它成为 resource route 的关键:
import type { Route } from "./+types/pdf-report"; export async function loader({ params }: Route.LoaderArgs) { const report = await getReport(params.id); const pdf = await generateReportPDF(report); return new Response(pdf, { status: 200, headers: { "Content-Type": "application/pdf", }, }); }getReport和generateReportPDF是你自己业务代码中的函数,Content-Type: application/pdf声明了响应体是 PDF。换成 JSON 响应时同理,返回Response并带上对应的Content-Type即可。
从页面链接到 Resource Route:必须用整页导航
resource route 不返回组件,所以不能用 React Router 的客户端路由去访问它。链接到 resource route 时必须使用<a>标签,或带reloadDocument属性的<Link>;否则 React Router 会尝试走客户端路由,此时你会收到一条有用的错误提示(文档原话:"you'll get a helpful error message if you make this mistake")。
<Link reloadDocument to="/reports/pdf/123"> View as PDF </Link>reloadDocument的含义见 docs/api/components/Link.md:点击时不走客户端路由,浏览器像处理普通<a href>一样完成整页导航。
处理不同的 HTTP 方法
同一个 resource route 可以用loader和action分别响应不同方法(docs/how-to/resource-routes.md):
- GET请求由
loader处理; - POST、PUT、PATCH、DELETE由
action处理。
import type { Route } from "./+types/resource"; export function loader(_: Route.LoaderArgs) { return Response.json({ message: "I handle GET" }); } export function action(_: Route.ActionArgs) { return Response.json({ message: "I handle everything else", }); }上面这段代码同时也是文档给出的 JSON 响应示例:Response.json直接生成带 JSON 编码的响应。
返回类型怎么选:Response 还是 data()
Resource route 对返回类型是灵活的,可以返回Response实例,也可以返回data()对象。文档给出的判断标准是:
- 面向外部消费的 resource route,返回
Response实例——响应编码在代码里是显式的,不用去猜 React Router 内部如何把data()转换成Response; - 从
fetcher或<Form>提交访问的 resource route,返回data()——与 UI 路由的 loader/action 保持一致,并允许通过data()/Await把 promise 流式传送到 UI。
data()的签名是function data<D>(data: D, init?: number | ResponseInit),用于在不强制序列化为真正Response的情况下携带headers/status(docs/api/utils/data.md):
import { data } from "react-router"; export async function action({ request }: Route.ActionArgs) { let formData = await request.formData(); let item = await createItem(formData); return data(item, { headers: { "X-Custom-Header": "value" }, status: 201, }); }错误处理:什么时候触发 handleError,什么时候不触发
这一节直接决定你的 resource route 出错时客户端和服务器各自看到什么(docs/how-to/resource-routes.md):
抛出Error(或Response/data()以外的任何东西)会触发handleError,并产生 500 HTTP 响应:
export function action() { let db = await getDb(); if (!db) { // Fatal error - return a 500 response and trigger `handleError` throw new Error("Could not connect to DB"); } // ... }handleError是在entry.server.tsx中导出的可选函数,用于接管服务端错误的日志记录与上报(默认行为是打到 console)。
只要 resource route 生成了一个Response(通过new Response()或data()),就视为执行成功,不会触发handleError——因为 API 已经为 HTTP 请求产出了 Response。这对 thrown response 和带 4xx/5xx 状态码的 returned response 同样成立。文档说明这个行为与fetch()一致:fetch()收到 4xx/5xx 响应时也不会返回 rejected promise。
export function action() { // Non-fatal error - don't trigger `handleError`: throw new Response( { error: "Unauthorized" }, { status: 401 }, ); }文档还指出throw new Response(...)、return new Response(...)、throw data(...)、return data(...)这几种写法在上述 401 示例中等价。
Error Boundary 只在 resource route 从 UI 访问时才相关:当它被fetcher调用或<Form>提交访问时,throw会冒泡到最近的 UIErrorBoundary。直接通过浏览器整页导航访问时不涉及 Error Boundary。
验证与限制
按上面的路径做完后,可以用文档给出的行为本身作为核对依据:
- 用 GET 请求访问该路由,应命中
loader;用 POST/PUT/PATCH/DELETE 请求,应命中action——两个方法各返回不同的 JSON 时即可确认分派正确; - 在页面中点击指向 resource route 的普通
<Link>(不带reloadDocument),应得到文档所说的 helpful error message;换成<a>或<Link reloadDocument>后走整页导航; - resource route 中
throw new Error(...)时应得到 500 响应并触发handleError;throw/return401Response时客户端直接收到 401,且不触发handleError。
限制方面注意两点前提:Resource Routes 只在服务端渲染(framework/data 模式)场景下按上述约定生效;如果模块不小心导出了 default 组件,它就不再是 resource route,上述"无 default export 即资源路由"的约定不再成立。
想进一步控制响应细节,可继续查阅 entry.server.tsx 中的handleError(自定义错误处理)与handleDataRequest(修改 data request 响应)两个可选导出;链接行为见 Link 的reloadDocument参数说明。
【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考