React Doctor 的 React Router 规则研究与实践:从 38 个候选规则到版本感知的 lint 契约
【免费下载链接】react-doctorYour agent writes bad React. This catches it项目地址: https://gitcode.com/GitHub_Trending/re/react-doctor
导读
本文基于 docs/react-router-rule-research.md 研究文档,系统梳理 React Doctor 为 React Router 设计专用 lint 规则的完整思路:为什么 React Router 不能当作单一框架形态的 API 来检测、如何通过"模式感知(Framework/Data/Declarative)"与"版本门控"两条基石保证规则精确、38 个候选规则如何按 P0/P1/P2 分层,以及这些契约在仓库中如何被真正落地实现。读完本文,你将掌握 React Router 运行时不变量的可 lint 化方法、版本矩阵驱动的规则门控策略,以及"缺报优于误报"的项目感知检测边界设计。
研究结论:React Router 值得专属规则家族,但必须模式感知
React Doctor 的研究结论非常明确:React Router 是专属规则家族的强候选,但不能被当作"一个框架形态的 API"来处理。官方 agent skill 将 React Router 明确划分为 Framework、Data、Declarative 三种模式,lint 规则也必须沿用这一区分——尤其是loaders、actions、fetchers、route.lazy以及路由模块导出,在 Declarative 模式下根本不存在,任何把 Framework/Data 指南强加到 Declarative 应用的规则都会产生灾难性误报。
研究分两轮推进:第一轮产出 15 个候选;第二轮深入路由运行时不变量、中间件实现、v6/v7/v8 变更日志、安全公告、Framework 约定、已接受的官方设计决策以及维护者讨论串,将积压扩充到38 个候选。其中 P0 层 12 条规则,其危害行为可直接证明、版本边界可编码,表格中保留了原始 P1/P2 上下文:
| 优先级 | 候选规则 | 模式 | 精度 | 置信度 |
|---|---|---|---|---|
| P0 | react-router-no-router-in-render | Data | scope + path | high |
| P0 | react-router-no-navigate-in-render | all | path | high |
| P0 | react-router-no-unsynchronized-search-params-mutation | all | scope + path | high |
| P0 | react-router-no-multiple-set-search-params-in-tick | all | path | high |
| P0 | react-router-no-invalid-lazy-route-properties | Data | scope | high |
| P0 | react-router-nested-route-requires-outlet | all | cross-file | high when resolved |
| P0 | react-router-no-loader-request-body | Framework, Data | scope | high |
| P0 | react-router-no-session-mutation-in-loader | Framework | scope | high |
| P0 | react-router-no-use-loader-data-in-error-ui | Framework, Data | scope + project | high |
| P0 | react-router-no-multiple-middleware-next | Framework, Data | path | high |
| P0 | react-router-descendant-routes-require-splat | all | cross-file | high when resolved |
| P0 | react-router-no-invalid-absolute-child-path | all | project | high when static |
| P1 | react-router-require-root-error-boundary | Framework, Data | project | high |
| P1 | react-router-guard-aborted-handle-error | Framework | path | high |
| P1 | react-router-resource-link-requires-reload | Framework, Data | project + scope | high when resolved |
| P1 | react-router-loader-parallel-fetch | Framework, Data | path | high |
| P1 | react-router-loader-fetch-forwards-signal | Data, client loaders | scope | medium-high |
| P1 | react-router-prefer-route-lazy | Data | scope | medium-high |
| P2 | react-router-internal-route-anchor | all | project + scope | medium-high when resolved |
| P2 | react-router-csp-nonce-consistency | Framework | cross-file | medium-high |
| P2 | react-router-valid-route-object | Data | scope | high, but mostly type-covered |
研究强调"候选数量本身不是目标"。扩充的意义在于分离三种不应混为一谈的产品形态:
- 默认开启的行为型规则:具有直接的运行时或安全后果;
- 版本作用域的迁移规则:仅在用户声明目标大版本时激活;
- 依赖/配置诊断:更适合交给既有 supply-chain 或项目扫描器实现,而不是 AST lint 规则。
"When resolved(解析成功时)"是这一切的承重墙:当 React Doctor 无法证明路由目标或组件实现时,项目感知规则必须保持沉默。缺失一条诊断远比猜测"这个锚点指向 UI 路由""这个导入的组件漏掉了 Outlet""这个 URL 是资源路由"要好。
必需的地基:模式感知能力检测与版本解析
模式感知能力检测
落地的基础设施只在包证据可靠处使用项目能力(见 capabilities.ts 与 collect-project-facts.ts 的实现):
react-router:安装了react-router或react-router-dom;react-router-framework:安装了@react-router/dev;- 单调递增版本令牌:
react-router:6.4到react-router:8,覆盖各规则使用的发布边界。
官方 skill 提供了精确的模式信号,并明确警告不要将 Framework/Data 指南应用于 Declarative 应用。因此Data 与 Declarative 用法必须由路由创建器、路由对象、JSX 路由器和导入 API 在本地(local)证明,而不是靠项目级模式猜测。对文件名敏感的 Framework 规则只限于规范文件;解析自定义appDirectory与任意路由配置属于未来的跨文件索引工作。
源码兼容性同样关键:
- React Router 6 应用通常从
react-router-dom导入 DOM API; - React Router 7 将这些 API 收敛到
react-router与react-router/dom,同时保留兼容包; - React Router 8 移除了
react-router-dom。
因此规则必须能从两个包解析导入,只在行为真正因主版本不同时才使用已安装主版本信息。研究时的官方文档版本为 v8.2.0,而下述契约刻意面向 v6.4 起的稳定 Data API 与 v7 起的 Framework API。
版本解析器与规则门控
版本感知不等于检查导入拼写。已实现的解析器从根与 workspace 的 manifest 读取声明版本和目录引用,保守地选择最低可解析版本,将无法解析或非 semver 的依赖视为未知。一条规则随后可以选择四种门控策略之一:
- API 存在门控(API-presence gate):从首次提供该 API 的版本起激活;
- 行为门控(Behavior gate):仅从使被诊断行为成立的那个版本起激活;
- 目标大版本迁移门控(Target-major migration gate):仅当用户显式声明目标大版本、或已安装的大版本已移除该构造时激活;
- 公告门控(Advisory gate):在 supply-chain 扫描器中将已安装包与补丁范围对比;不要把易受攻击的依赖伪装成源码 lint 问题。
研究给出了完整的功能/版本矩阵,直接对应仓库中 constants.ts 定义的能力阈值阶梯(react-router:6.4、6.7、6.9、6.19、7、7.8、7.9、7.10、7.15、8):
| 功能或行为 | v6 | v7 | v8 | 规则后果 |
|---|---|---|---|---|
| Data routers、loaders、actions | >=6.4 | 支持 | 支持 | Data-only 规则需要 Data/Framework 证明,而非仅仅包导入。 |
route.lazy函数 | >=6.9 | 支持;之后增加对象形式 | 支持 | lazy 属性与 route-lazy 规则在 6.9 以下保持关闭。 |
useBlocker | 不稳定>=6.7,稳定>=6.19 | 支持 | 支持 | 多 blocker 规则仅在各自有效范围内识别两个名称。 |
| search-param setter 回调隔离 | 7.7 之前共享可变实例 | 从7.7.0起为复制的回调值 | 复制值 | 变更规则只跟踪元组结果、绝不跟踪 setter 回调参数,同一检测器跨版本有效。 |
| Middleware | 不存在 | 7.9 前不稳定;稳定>=7.9 | 始终启用 | 稳定 middleware 规则在 7.9 门控;实验性 middleware 留在首个已发布契约之外。 |
next()永不抛出 | 不存在 | 从7.8.0起为真 | 真 | 围绕next的 try/catch 规则对更早的实验性 middleware 必须关闭。 |
| React transition 路由选项 | 不存在 | unstable_useTransitions自 7.10;useTransitions自 7.15 | 作为useTransitions支持 | Promise 返回规则使用正确 prop 名,且只把 Data/Framework 导航 API 视为返回 Promise。 |
Framework 根Layout错误流 | 不存在 | 支持 | 支持 | loader-data-in-error-UI 自 v7 起适用于 Framework 根Layout与路由边界。 |
| 包入口点 | react-router-dom正常 | 保留兼容导出 | react-router-dom被移除 | 导入迁移规则仅对已安装 v8 激活;仍在维护的 v6/v7 应用保持安静。 |
meta/useMatches匹配数据 | data | data自 7.8 弃用;loaderData可用 | data被移除 | 移除字段规则仅对已安装 v8 激活,且要求 scope 或 Framework 导出证明。 |
future.v8_*标志 | 不存在 | 可选的升级控制 | 移除或提升 | 移除标志规则仅对已安装 v8 Framework 配置激活。 |
由该矩阵推出两条重要的"非规则":
- 不要仅仅因为当前文档偏好 v8 拼写就运行 v8 codemod 风格的诊断——一个正常维护的 v6 应用从
react-router-dom导入是完全正确的; - 不要按大版本 fork 每一个行为检测器——大多数路由树不变量从 v6 到 v8 保持不变;只有在 API 或行为真正不同时才附加最低版本。
路由与组件索引
有三个候选需要一个小型跨文件索引:
- 路由父级 → 组件/模块;
- 路由路径 → UI 路由或资源路由;
- 组件 → 直接或传递渲染
Outlet/调用useOutlet。
Framework 模式可以从routes.ts、@react-router/fs-routes以及配置的 app 目录推导;Data 模式可以从提供给 contenteditable="false">【免费下载链接】react-doctorYour agent writes bad React. This catches it项目地址: https://gitcode.com/GitHub_Trending/re/react-doctor
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考