ESLint 10下的React 19代码检查规则,以及当eslint-plugin-react版本落后时的情况
为什么 eslint-plugin-react 在 ESLint 10 中会出问题,以 Biome 为优先的配置方式如何将 React 的代码检查规则限制为 11 条,以及如何将该分支集成到 flat config 和 Next.js 中。
将 React 代码库升级到 ESLint 10 时,常常会因为一个依赖项而受阻:eslint-plugin-react。在撰写本文时,其最新版本还不支持 ESLint 10,上游的修复也仍在等待合并。本文将解释这一问题的原因,介绍如何通过一个独立的分支 @ternaus/eslint-plugin-react,在 Biome 承担大部分代码检查任务后,仅保留对 React 19 重要的规则集,并展示如何在纯 flat 配置及 Next.js 环境中安装该插件。
为何在自动化工具编写代码时规则检查更为重要
团队将更多工作交给代码生成工具时,其代码库中的各项要求就必须具备可执行性。人工审查成本高昂,且难以及时发现那些常见的错误。通过预提交钩子、测试、确定的规范检查以及小型且可审查的提交,可以将这些要求转化为工具能够识别的通过/失败信号,同时让每次故障都足够小以便于诊断。代码检查正是这类防护措施之一,因此在进行工具链升级时丢失 React 的代码检查功能绝非仅仅是些小麻烦。
ESLint 10 下会出现哪些问题
ESLint 10 版本中的重大变更之一是移除了规则上下文对象中那些早已被废弃的方法。目前发布的最新 React 插件版本 eslint-plugin-react@7.37.5 将 ESLint 9 标记为它所支持的最高版本,而该插件中的一些规则仍在调用那些方法。在 ESLint 10 环境下,这会导致类似这样的崩溃:
TypeError: contextOrFilename.getFilename is not a function
上述方法 context.getFilename() 在现代规则 API 中已被 context.filename 属性取代,因此插件代码必须进行修改;没有配置选项可以恢复原来的功能。
Upstream在2026年2月7日于GitHub上提交了相关问题报告;7月30日有一份修复方案以拉取请求的形式出现。截至2026年8月底,这两个问题都尚未关闭。在采取行动之前请先查看它们的状态:如果在你阅读此文时Upstream已经发布了对ESLint 10的支持,最简单的办法可能是升级原有的插件。
此处介绍的分支针对特定的支持环境:
- React 19及更高版本
- ESLint 10及更高版本,仅支持扁平配置格式
- Biome 2.5.8及更高版本
- Node.js 22.13、24和26版本
该分支所假设的以Biome为优先的配置方式
规则选择只有针对特定代码栈才有意义。Biome是主要的格式化工具和代码检查器,能够处理常规的JavaScript、TypeScript、JSX、DOM以及大多数React相关检测。ESLint仅用于处理Biome无法覆盖的部分:框架插件以及少量React 19的特定检查。
参考项目运行环境如下:
- 基于Next.js 16的React 19项目,使用TypeScript编写
- 配置了
all预设的Biome - 使用扁平配置文件的ESLint 10
- 以Yarn 4作为包管理器
- Node.js 22、24和26版本
在这种架构下,不需要额外的、功能重叠的代码检查工具,只需在Biome运行后提供更多信息的React特定检测即可。
从102条活跃规则降至11条
该分支源自上游仓库,保留了其Git历史记录、MIT许可证及归属信息,并独立于该仓库进行维护。在分支产生的那个提交中,上游导出了104个规则模块。all预设启用了其中的102个(另外两个已被废弃),而recommended则列出了22条规则,其中react/no-unsafe被明确禁用,最终有21条规则会被强制执行。
如果将所有规则都包含进来,虽然不会减少包的大小,但也会使其缺乏明确的功能定位。因此,这些规则是根据其所强制执行的决策类型进行分类的:
- 如果Biome已经提供了相同的诊断信息,则该规则会被移除。
- 如果是关于格式、命名、文件结构或团队政策的内容,那么这些规则应归入Biome或应用程序自身的配置中。
.eslintrc 配置、解析器变通方案或已过时的 React API,那么它就不在 React 19 的规范范围内。恰好有 20 条规则属于第一类,其中包括 jsx-key、no-danger、no-unknown-property 和 self-closing-comp。在分支项目的文档文件夹中有一份映射文件,将每条规则与其对应的 Biome 规则对应起来。
那些用于规定风格或政策的规则,例如 prefer-stateless-function、jsx-sort-props 或 function-component-definition,已被剔除,因为它们与 React 19 的合规性无关。no-unused-prop-types 和 no-unused-state 也被移除,因为单文件 AST 检查无法可靠地回答涉及整个项目范围的问题;过多的规则会让人和自动化工具习惯于忽略代码检查的输出。其余被排除的则是不在指定支持范围之外的旧版兼容代码。
仅有四个上游规则 ID 保留下来:no-deprecated、no-invalid-html-attribute、no-direct-mutation-state 以及 jsx-no-constructed-context-values。
新规则,以及一处被刻意舍弃的规则
有三项尚未合并的上游提案与 React 19 的升级相关:
- 标记会渲染出
undefined的组件(上游问题 #3020) - 禁止在函数组件中使用
defaultProps(问题 #3911) - 建议为
useState使用延迟初始化方式(PR #3579)
上述三项均已实现,随后 no-render-return-undefined 又被移除。React 19 允许组件返回 undefined,因此禁止这一行为无异于以框架规则为名的内部规定。另外两项分别以 no-function-default-props 和 prefer-use-state-lazy-initialization 的形式实现:no-function-default-props 用于标记 React 19 在函数组件中会忽略的 API,而 prefer-use-state-lazy-initialization 则是对每次渲染时可能产生的不必要的操作的警告,例如使用 expensive() 而非 () => expensive()。
另有五条规则针对 React 19 的特定行为,这些行为或是新出现的,或是相较于上游版本有所加强:no-prop-types、no-misspelled-lifecycle-methods、jsx-no-key-after-spread、controlled-form-requires-handler 以及 no-implicit-ref-callback-return。ref-callback 规则很好地说明了为何现在这一点如此重要:由于 React 19 允许 ref 回调返回清理函数,那些隐式从 ref 回调中返回值的箭头函数已不再是无害的。
因此,8.0.0 版本共包含了 11 条规则,全部属于 recommended 等级。其中九条是用于检测错误的规则,两条则是用于提示性能问题的警告。由于 all 预设要么会与 recommended 重复,要么仅在严重程度上有所差异,因此该包并未提供这一预设。
哪些实际项目发现了测试未能捕捉的问题
在 8.0.0-rc.3 版本中,单元测试和包检查均已通过。而将该插件应用于实际应用时,才有有用的错误开始出现。
最初的问题是 HTML 属性元数据。规则 no-invalid-html-attribute 拒绝了完全有效的属性,包括 alt、accept、name、loading、form,以及 <select>、<option> 和 <textarea> 元素上的 value 属性。为解决这个问题,该分支的跟踪系统中经历了三轮问题报告与修复提案的讨论(#21/#22、#25/#26 和 #29/#31)。最终的解决方案是将 WHATWG HTML 内容属性与 React DOM 属性视为两个独立的真实数据源,而非假设存在一个元数据表可以同时描述两者。
第二次故障来自 Next.js。eslint-config-next@16 生成的是扁平化配置,但它却从传统格式的 react.configs.recommended.rules 字段中读取规则。该分支仅暴露了 react.configs.flat.recommended,因此在任何文件被检查之前配置就已经出问题了。后续的修改(issue #24,PR #27)添加了该字段用于仅读取用途,但并未恢复对 .eslintrc 的支持。由于 Next.js 是按未加作用域的名称导入该插件的,因此还需要进行 Yarn 解析,具体如下所示。
这些集成产生了第4版到第6版的候选发布版本,并重新设计了测试策略。在最终发布之前,会使用publint对打包好的npm压缩包进行代码检查,该压缩包会被用ESM、CommonJS和TypeScript编写的测试工具导入,并通过Next.js所支持的配置格式进行测试,所有流程都在Node.js 22.13、24和26环境下通过CI完成。这一经验可推广到任何工具包:应测试你发布的成果,包括那些使用你的工具包的客户端,而不仅仅是源代码本身。
最终生成的包是纯ESM格式的,仅采用扁平配置方式,并保留了熟悉的react/*规则命名空间。
安装与配置
这些命令需要使用Yarn 4。首先将Biome、ESLint 10以及相关插件作为开发依赖项添加:
yarn add --dev @biomejs/biome@'>=2.5.8' eslint@^10 @ternaus/eslint-plugin-react@^8.0.0
在 biome.json 中启用 Biome 的完整稳定规则集及其 React 相关规则,这样 Biome 就能覆盖该分支有意遗漏的所有内容:
{
"linter": {
"domains": {
"react": "all"
},
"rules": {
"preset": "all"
}
}
}
接着将剩余的 React 规则添加到 eslint.config.js 中。展开运算符会将预设中的插件注册信息与规则合并到一个由 files glob 指定的配置对象中:
import react from '@ternaus/eslint-plugin-react';
export default [
{
files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
...react.configs.flat.recommended,
},
];
如果该 glob 包含 .ts 或 .tsx 文件,需在更早的配置对象中注册支持 TypeScript 的解析器;预设不会自动为你配置此类解析器。
通常以独立的 CI 步骤或单个脚本的形式同时运行这两个工具:
yarn biome check .
yarn eslint .
规则编号仍保留 react 前缀,因此为原始插件编写的覆盖规则同样适用于那些仍然存在的规则:
{
rules: {
'react/no-deprecated': 'error',
'react/no-implicit-ref-callback-return': 'error',
},
}
将其集成到 Next.js 中
eslint-config-next会以eslint-plugin-react的名称导入该插件。使用Yarn时,可通过resolutions将此名称重定向到对应的fork版本:
{
"devDependencies": {
"@ternaus/eslint-plugin-react": "8.0.0"
},
"resolutions": {
"eslint-plugin-react": "npm:@ternaus/eslint-plugin-react@8.0.0"
}
}
需确保两个版本号保持一致。这样的配置可以防止eslint-config-next同时引入仅适用于ESLint 9的原始版本和ESLint 10的fork版本。由于Next.js将该插件注册在react下,现有的react/*规则ID依然有效。
何时不应选择此fork版本
- 它并不包含所有的上游规则。那些依赖
react/prop-types、react/display-name或react/jsx-sort-props的配置,在切换之前应先查看该fork版本仓库中支持的规则列表。
key 属性之类的实际问题。关键要点
- ESLint 10 的崩溃是由于移除了规则上下文相关 API,因此只有通过插件更新才能解决该问题。
- 以 Biome 为主的开发环境所需的 ESLint React 规则数量要少得多;按规则所执行的决策类型对规则进行分类,是一种可重复使用的方法,有助于精简重叠的代码检查配置。
- 那些需要整个项目范围作为依据的规则会在逐文件检查工具中产生不必要的干扰,最好直接移除而非勉强保留。
- 以用户实际看到的形式测试已发布的工具:打包后的归档文件、各种模块格式,以及诸如
eslint-config-next这样的真实框架配置。 - 借助 Next.js,Yarn 的
resolutions别名允许该分支替代未加作用域的包,而无需更改规则编号。
源代码和发布说明均保存在该分支的仓库中。