代码规范这件事,说小很小,说大也很大。小到缩进用空格还是Tab、单引号还是双引号,大到是否遗漏了Hook依赖、是否误用了已被弃用的内部API。靠人肉Code Review来盯这些事,效率低且容易遗漏,而ESLint正好能把这类机械性检查交给机器来做。在React项目中,用好ESLint不只是装个包配个文件,更重要的是理解它的运行机制,进而在团队规范超出内置规则覆盖范围时,有能力自己写规则、做插件。

一、ESLint是如何工作的:从AST说起
ESLint的核心思路是把源代码解析成抽象语法树(AST),然后交给一条条规则去遍历这棵树,发现不符合预期的地方就报告问题。理解这一点非常重要,因为自定义规则本质上就是写一个访问AST节点的监听器。
ESLint默认使用Espree作为解析器,而React项目中通常配合@babel/eslint-parser或@typescript-eslint/parser来处理JSX、TypeScript等语法。无论用哪个解析器,最终产出的AST结构都遵循ESTree规范,只是会附加一些扩展节点(比如JSXElement)。你可以通过AST Explorer这个在线工具直观查看一段代码解析后的树结构,这是开发规则前必做的功课。
一条规则的形态大致如下:一个对象,包含meta信息和一个create函数。create函数返回一个对象,键是AST节点类型,值是访问到该类型节点时的回调函数。举个例子,如果你想检查所有函数命名是否以小写字母开头,只需要监听FunctionDeclaration节点,读取node.id.name做判断即可。问题报告通过context.report发出,可以携带节点位置、消息、修复建议等信息。
二、React项目的ESLint环境搭建
在动手写自定义规则之前,先把基础环境配好。一个典型的React项目需要安装eslint、eslint-plugin-react以及支持JSX的解析器。下面是一份可直接使用的配置:
{
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 2022,
"sourceType": "module",
"ecmaFeatures": { "jsx": true }
},
"plugins": ["react"],
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"rules": {
"react/jsx-uses-react": "off",
"react/prop-types": "warn"
},
"settings": {
"react": { "version": "detect" }
}
}这份配置里有几个细节值得注意。首先,extends和rules的优先级关系是后者覆盖前者,所以团队内部约定永远放在rules里做最终裁定。其次,settings中把React版本设为detect,插件会自动从package.json读取版本,避免版本升级后规则行为不一致。最后,如果项目用了新版本React且启用了新的JSX转换方式,就不再需要每处引入React,相关的jsx-uses-react规则可以关闭。
除了eslint-plugin-react,实际项目里还强烈建议加上eslint-plugin-react-hooks。它提供的rules-of-hooks规则能检测出在条件分支或循环中调用Hook的错误用法,exhaustive-deps规则则能发现useEffect遗漏依赖的隐患。这两类问题在运行时往往不报错,但会导致状态错乱、数据不更新等诡异Bug,靠自动化检查拦截的价值极高。
三、动手开发一条自定义规则
假设团队决定弃用某个内部组件库的OldButton组件,要求所有新代码改用Button。内置规则显然覆盖不了这种业务约束,这时就轮到自定义规则出场了。规则代码可以这样写:
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "禁止使用已弃用的 OldButton 组件"
},
fixable: "code",
messages: {
deprecated: "OldButton 已被弃用,请使用 Button 组件替代。"
}
},
create(context) {
return {
// 匹配 import OldButton from 'xxx'
ImportDeclaration(node) {
if (node.source.value === "my-ui/old-button") {
context.report({ node, messageId: "deprecated" });
}
},
// 匹配 JSX 中的 <OldButton /> 用法
JSXIdentifier(node) {
if (node.name === "OldButton") {
context.report({
node,
messageId: "deprecated",
fix(fixer) {
return fixer.replaceText(node, "Button");
}
});
}
}
};
}
};这段代码里有两个关键点。第一,规则的检测分成了两个层面:import语句层面和JSX标识符层面,只有两方面都覆盖,才能做到真正的拦截。第二,meta中声明了fixable,并在报告中提供了fix函数,这样开发者执行eslint --fix时可以自动完成组件名替换,把规范落地成本降到最低。
写好规则后需要验证。推荐用ESLint官方的RuleTester来写单元测试,它能模拟各种代码场景并断言报告结果。测试用例要覆盖正反两面:违规代码应该报告在正确的行列位置,合规代码不应产生任何报告,包含fix的场景还要断言修复后的输出。这一步不能省,规则一旦有误报,开发者的信任感会迅速流失,后续推行规范的阻力会大得多。
四、把规则封装成可复用的插件
单条规则如果直接在项目里引用,路径管理会比较混乱,团队间也无法共享。规范的做法是封装成ESLint插件。插件本质上是一个npm包,其入口导出一个rules对象,把自定义规则按名称注册进去。目录结构通常是:根目录放package.json,lib下的rules目录存放各个规则文件,再配一个index.js做汇总导出。
const noOldButton = require("./rules/no-old-button");
const requirePropComment = require("./rules/require-prop-comment");
module.exports = {
rules: {
"no-old-button": noOldButton,
"require-prop-comment": requirePropComment
},
configs: {
recommended: {
plugins: ["my-team"],
rules: {
"my-team/no-old-button": "error",
"my-team/require-prop-comment": "warn"
}
}
}
};注意configs这个字段的设计。给插件提供一份预设配置,使用方只需要在extends里写plugin:my-team/recommended,就能一次性启用所有推荐规则,不必逐条手写。规则的报错等级、开关状态都由插件作者统一把控,后续调整规范时使用方升级版本即可,这是插件模式相比散装规则最大的优势。
发布到私有npm仓库或公共registry后,其他项目安装依赖并在配置中引用即可。如果是公司内部使用,建议搭一个私有registry(比如Verdaccio),把团队规范类插件统一管理,配合版本号做规范的迭代管理,避免出现各项目规则版本五花八门的局面。
五、接入CI流程,让规范真正落地
本地检查依赖开发者的自觉,要形成强制约束必须接入CI。常见做法有两种:一种是在Git的pre-commit钩子里跑eslint,借助husky和lint-staged只检查暂存区文件,速度快、不打断工作流;另一种是在CI流水线中单独加一个lint阶段,无论何种提交都全量检查。两种方式不冲突,推荐组合使用,钩子做快速反馈,CI做最终兜底。
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix"]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}落地过程中有几个坑值得提前规避。规则误报要给出清晰的提示信息和修复建议,否则开发者只会想办法绕过;新规则上线建议先以warn级别观察一两周,确认没有大面积误报后再升级为error;存量代码可以借助eslint-suppression-line或者在规则里设计豁免机制,分批治理而不是一刀切。规范的价值在于长期一致,推行节奏比规则本身更能决定成败。
ESLint自定义规则React代码规范ESLint插件开发修改时间:2026-09-10 19:28:40