React Router 6.4 架构决策:如何把 Remix 分层到 React Router 6.4 之上

发布时间:2026/11/4 16:08:50

React Router 6.4 架构决策:如何把 Remix 分层到 React Router 6.4 之上 React Router 6.4 架构决策如何把 Remix 分层到 React Router 6.4 之上【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router本篇基于 React Router 仓库中的架构决策记录 0007-remix-on-react-router-6-4-0.md完整还原 2022 年 8 月团队在react-router6.4.0发布前夕做出的关键工程决策如何以绞杀者模式strangler pattern把 Remix 框架的 Data API 层逐步替换为 React Router 6.4 新引入的createStaticHandler等能力。读完你会掌握迁移问题的功能拆解方法服务端数据加载 / 服务端渲染 / 客户端水合 / 客户端数据加载四个切面、feature-flag 双跑断言的灰度迁移手法以及该决策在当前仓库源码中的最终落地形态。一、背景为什么要在 6.4.0 之后动 Remix 的地基该决策记录Date: 2022-08-16Status: accepted的起点是 0005-remixing-react-router.md 中提出的Remixing React Router计划——把 Remix 的 Data API路由匹配、loader/action 调度、错误边界等下沉合并进react-router核心库。到写这份 ADR 时合并工作已基本完成react-router6.4.0即将发布。此时出现了一个明显的冗余Remix 自身的运行时里还保留着一整套处理 Data API 的旧代码请求分发、loader 执行、边界追踪等。ADR 的核心目标非常直接把 Remix 分层layer到最新版 React Router 之上从而可以删除 Remix 中大量处理 Data API 的重复代码。ADR 同时强调这不是一次big-bang merge一次性大重构而是要设计可迭代、可回滚的渐进式实施方案。二、迁移目标拆解四个功能切面及其部署约束ADR 从迭代发布视角把问题拆成 4 个独立的功能方面Server data loading服务端数据加载Server react component rendering服务端 React 组件渲染Client hydration客户端水合Client data loading客户端数据加载四者之间存在明确的依赖关系这是制定部署节奏的依据(1) 可以独立实现并独立部署——它只涉及服务端运行时不依赖客户端代码同步变化(2) 和 (3) 必须一起做——因为 SSR 产出的 HTML 上下文contexts/components必须与客户端水合时读取的上下文严格匹配网络两侧的 React 树结构不能半新半旧(4) 几乎免费得到——一旦 (3) 中客户端创建路由时把 loaders/actions 挂了上去客户端数据加载能力自然随之而来。这个拆解直接决定了后文先服务端、后渲染层的推进顺序。三、高层决策四步走ADR 的 Decision 部分给出了高层推进路线SSR 数据加载迁移更新handleResourceRequest在 feature flag 之后改用createStaticHandler目标尽可能让单元测试与集成测试同时断言新旧两条流程以同样方式更新handleDataRequest以同样方式更新handleDocumentRequest确认所有单测和集成测试通过把新的RemixContext数据写入EntryContext并移除旧流程对remix-run/server-runtime的改动观察稳定后再部署remix-run/react的改动放在一个短生命周期的 feature 分支中推进先做不带水合的服务端渲染用RemixContext替换EntryContext再接客户端水合最后补上向后兼容层对remix-run/react的改动观察稳定后再部署四、改动落点两个包、一道网络鸿沟ADR 指出需要改动的两个主要区域remix-run/server-runtime中服务端请求处理主要在server.ts文件remix-run/react中客户端水合 路由主要在components.ts、server.ts、browser.ts文件。关键洞察是这两个区域被网络network chasm天然隔开。服务端渲染出的 HTML 与客户端 hydration 是异步交接的因此两侧可以各自独立实现、独立小步合并、独立开发出问题时的回滚成本也更低——这是整份 ADR 能够不做大爆炸式合并的根本原因。五、为什么先做服务端数据获取迁移ADR 给出了两个明确理由改动面更小——新方案本质上只需要对接一个新 APIcreateStaticHandler更容易做成 feature-flag 形式——服务端代码不受 bundle 体积约束可以放心地在代码里保留新旧双跑的对照逻辑。在此基础上ADR 选择了绞杀者模式strangler pattern保留旧流程不动在新分支逻辑中用 flag 开关双跑新流程并通过断言证明新方案与旧方案功能等价等建立起足够信心后再删除旧代码和 flag 条件。5.1 双跑对照的伪代码ADR 原文示例ADR 给出的示例flag 初始提交为false本地开发和测试中切换为true一旦新静态处理器static handler产出的 SSR 数据与旧流程不一致就抛出异常// Runtime-agnostic flag to enable behavior, will always be committed as // false initially, and toggled to true during local dev const ENABLE_REMIX_ROUTER false; async function handleDocumentRequest({ request }) { const appState { trackBoundaries: true, trackCatchBoundaries: true, catchBoundaryRouteId: null, renderBoundaryRouteId: null, loaderBoundaryRouteId: null, error: undefined, catch: undefined, }; // ... do all the current stuff const serverHandoff { actionData, appState: appState, matches: entryMatches, routeData, }; const entryContext { ...serverHandoff, manifest: build.assets, routeModules, serverHandoffString: createServerHandoffString(serverHandoff), }; // If the flag is enabled, process the request again with the new static // handler and confirm we get the same data on the other side if (ENABLE_REMIX_ROUTER) { const staticHandler unstable_createStaticHandler(routes); const context await staticHandler.query(request); // Note: only used for brevity ;) assert(entryContext.matches context.matches); assert(entryContext.routeData context.loaderData); assert(entryContext.actionData context.actionData); if (catchBoundaryRouteId) { assert(appState.catch context.errors[catchBoundaryRouteId]); } if (loaderBoundaryRouteId) { assert(appState.error context.errors[loaderBoundaryRouteId]); } } }注意断言覆盖的四个维度matches路由匹配、routeDataloader 数据、actionDataaction 数据、以及按边界路由 id 索引的errorscatch 边界与 error 边界各自对应。这正是功能等价验收的最小完备集。5.2 服务端的进一步迭代切分服务端内部还可以再细分handleResourceRequest、handleDataRequest、handleDocumentRequest三者可以独立实现也可以独立发布且按这个顺序推进恰好从简单到复杂。5.3 实施细节与注意事项ADR NotesADR 对 flag 方案补充了两个工程细节不能用process.env——被改动的代码是 runtime-agnostic 的所以先用server.ts里的本地硬编码变量规避 runtime 特定的环境变量问题测试需要各自的 flag 副本——例如存在某路由 loader 只被调用一次的单元测试flag 开启后 loader 会被调用两次新旧流程各一次测试断言需要按 flag 做条件化entry.server.ts传递的remixContext形状会变化——团队将其视为不透明的opaqueAPI因此不认为这是 breaking change。5.4 具体实现步骤ADR Implementation approach用createHierarchicalRoutes构建 RR 的DataRouteObject实例ADR 指向brophdawg11/rrr分支中的createStaticHandlerDataRoutes每个请求用unstable_createStaticHandler创建 static handlerhandleResourceRequest——应该非常简单因为它只需把queryRoute返回的原始Response透传回去handleDataRequest——比资源路由稍复杂需要处理错误序列化并把重定向redirect处理为客户端的 204 响应handleDocumentRequest——最大的一个。它最终能简化很多但不匹配点也最集中需要把 query 的错误映射到 Remix 对 error/catch 的定义上并相应向上冒泡。举例URL/a/b/c中若 C 导出了CatchBoundary但没有ErrorBoundary它会被表示为hasErrorBoundarytrue的DataRouteObject因为remix-run/router不做区分若 C 的 loader 抛出错误router 会在 C 的errorElement处接住它但随后需要把它重新向上冒泡到最近的ErrorBoundaryADR 指向分支中的differentiateCatchVersusErrorBoundaries新的RemixContext包含manifest、routeModules、staticHandlerContext、serverHandoffString创建时与EntryContext并存并断言两者值一致若渲染过程中捕获到错误边界信息已被记录在staticHandlerContext上可以用getStaticContextFromError生成第二遍渲染所需的新上下文注意需要再次调用differentiateCatchVersusErrorBoundaries。六、决策在当前仓库源码中的落地验证ADR 是 2022 年的规划而当前仓库中这些设计已经演进为 React Router框架模式的标准服务端运行时可以直接在源码中逐一印证。1. flag 双跑消失新流程成为唯一流程。在 packages/react-router/lib/server-runtime/server.ts 中createRequestHandler内部通过derive()一次性完成 ADR 步骤 1 中规划的接线L63-L71function derive(build: ServerBuild, mode?: string) { let dataRoutes createStaticHandlerDataRoutes(build.routes); // ... let staticHandler createStaticHandler(dataRoutes, { basename: build.basename, mapRouteProperties: defaultMapRouteProperties, instrumentations: build.entry.module.instrumentations, future: build.future, }); // ... }ADR 中先handleResourceRequest、再handleDataRequest、最后handleDocumentRequest的顺序如今体现为统一的请求分派逻辑L226-L300以.data结尾的请求走handleSingleFetchRequest叶子路由没有default导出且没有ErrorBoundary时走handleResourceRequest其余走handleDocumentRequest。2.createHierarchicalRoutes的设想落地为createStaticHandlerDataRoutes。ADR 要求把路由 manifest 转换为 RR 的DataRouteObject实例当前实现位于 packages/react-router/lib/server-runtime/routes.tsL48-L143。其中值得注意的一处细节是ErrorBoundary的映射L121-L125// Always include root due to default boundaries ErrorBoundary: route.id root || route.module.ErrorBoundary ! null ? () null : undefined,即根路由永远带上占位错误边界——这正是 ADR 中router 不区分 catch/error 边界需要在数据层标记后重新冒泡方案的直接产物边界信息在构建DataRouteObject时就被打上标记供后续错误路由使用。3.handleDocumentRequest与handleResourceRequest的形态与 ADR 描述一致。资源路由路径确实非常简单——server.ts 中handleResourceRequest调用staticHandler.queryRoute(request, { routeId, ... })对结果是Response就透传、是字符串就包成Response、否则Response.json错误处理分支中还会把 loader/action 抛出Response的情况原样返回L697-L720与 ADR 把 queryRoute 的原始 Response 回传的设想吻合。文档请求路径则调用staticHandler.query(request, { requestContext, generateMiddlewareResponse, ... })L488-L503拿到StaticHandlerContext后组装EntryContext交给entry.server.tsx的默认导出函数渲染。4.getStaticContextFromError的二次渲染路径被完整保留。ADR 指出渲染中出错时用getStaticContextFromError生成包含错误在正确边界上的新上下文再渲染一遍。当前实现正是这样做的server.ts// Get a new StaticHandlerContext that contains the error at the right boundary context getStaticContextFromError( staticHandler.dataRoutes, context, errorForSecondRender, );随后重新生成entryContext含新的staticHandlerContext与serverHandoffString/serverHandoffStream再次调用handleDocumentRequestFunction若第二遍仍失败则返回最终的 500 兜底响应。该函数的行为在测试 packages/react-router/tests/router/ssr-test.ts 中有专门的describe(getStaticContextFromError)用例覆盖验证了错误被放到正确的路由边界上。5. 新 API 的公开文档。ADR 中写作unstable_createStaticHandler的 API如今已是稳定公开 API见 docs/api/data-routers/createStaticHandler.md用于为给定路由树创建query/queryRoute等能力——这正是当时只需对接一个新 API所指的接口。七、第二步UI 渲染层的整体替换与兼容策略ADR 认为remix-run/react的渲染层是一次更彻底的整体替换whole-sale replacement且附带向后兼容负担所以排在第二步。但实现仍可迭代只是不能部署迭代——SSR 与客户端 HTML 必须保持同步相关 hooks 必须读自同一套上下文。推进顺序先让 SSR 文档在没有Scripts/的情况下正确渲染再加入客户端水合。主要改动包括移除RemixEntry及其上下文改用一个包裹DataStaticRouter/DataBrowserRouter的新RemixContext.Provider该上下文只需要 Remix 特有的部分manifest、routeModules旧RemixEntryContext中的其余内容全部转移到 router 的上下文中SSR 期间则是staticHandlerContext完全冗余、可直接改为从react-router-dom重导出的组件Form、useFormAction、useSubmit、useMatches、useFetchers大部分冗余但需要保留 Remix 特有行为的组件需要调整Link、useLoaderData、useActionData、useTransition、useFetcher。向后兼容要点清单ADR 逐条列出了必须保留的兼容行为这也是评估两个 router 是否真正等价的验收清单useLoaderData/useActionData需要保留泛型当时的react-router中它们还没有泛型useTransition需要补上submission和type字段——因为Form methodget在react-router-dom中不再进入 submitting 状态Remix 语义下必须保留useFetcher需要补上typeunstable_shouldReload被shouldRevalidate取代——ADR 还留了一个开放问题如果两个都存在能否优先用shouldRevalidate而兼容旧写法error 边界与 catch 边界的区分语义必须保持Request.signal——继续以独立的signal参数传递对应 ADR 伪代码中 loader 可拿到的取消信号。八、小结一份 ADR 如何约束一次大重构回看 decisions/0007-remix-on-react-router-6-4-0.md 的方法论它对任何在稳定基线上替换底层框架的任务都有参考价值按部署边界拆解利用网络鸿沟把问题切成服务端/客户端两个可独立回滚的战场并把必须一起上的部分SSR hydration明确标注用 strangler pattern 建立等价性证据feature flag 双跑 强断言matches/loaderData/actionData/errors 四维对照让删旧代码成为一个有测试背书的低风险动作从简单到复杂排序handleResourceRequest→handleDataRequest→handleDocumentRequest复杂度递增的同时风险也递增先易后难能尽早暴露映射不匹配的问题兼容清单前置在动 UI 层之前就列出泛型、状态字段、API 更名等全部兼容点避免渲染层替换变成黑盒。从当前仓库源码看packages/react-router/lib/server-runtime/server.ts、routes.ts与__tests__/router/ssr-test.ts这套方案已被完整执行flag 双跑阶段结束后createStaticHandler驱动的query/queryRoute流程成为服务端运行的唯一路径而 ADR 中预留的getStaticContextFromError二次渲染机制、边界标记策略至今仍是框架模式服务端渲染的核心骨架。【免费下载链接】react-routerDeclarative routing for React项目地址: https://gitcode.com/GitHub_Trending/re/react-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/4 22:31:35

Windows NTP服务器搭建实战:内网时间同步与W32Time配置指南

简介:一份详细讲解Windows NTP服务器配置的docx文档,面向涉及华三视频监控、工控机、指控系统DCC与通讯控制服务系统CCS等业务场景的运维及工程人员,目标是解决多节点时间不同步带来的火情图片与录像不匹配问题。文档从环境前提切入&#xff…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 13:55:19

GD32E10x官方例程入门:从GPIO点灯到串口调试

简介:这份GD32E10x系列官方例程面向嵌入式硬件与单片机开发者,基于ARM Cortex-M3内核,汇集兆易创新GD32E10x的示例代码,适合工业控制、消费电子、通信设备等场景的学习、验证与二次开发。压缩包共935个文件,约11.16MB&…

2026/11/5 13:50:18

基于Django的运维平台实战:资产、发布、巡检、告警一体化

简介:这份资源是一套基于Django框架开发的运维平台源码,面向具备Python基础、希望了解Web运维系统实现思路的开发者与运维人员,可用于学习主机管理、服务监控、日志查看、任务调度、权限控制、资源统计、脚本执行与故障告警等常见运维模块的落…

2026/11/5 13:25:11

基于ADS122U04IPWR与STM32F427ZI的工业测温方案设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/4 12:38:23

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑