通过配置文件来忽略对指定文件的代码检查
ESLint低于7.0.0
.eslintignore
/config
src/utils/**
.prettierignore(避免代码被 Prettier 的通用规则修改)
.eslintcache
*.lock
yarn-error.log
src/utils/**
ESLint大于7.0.0
.eslintrc.js
javascript
"ignorePatterns": ['src/utils/**']
no-access-state-in-setstate
Use callback in setState when referencing the previous state. react/no-access-state-in-setstate
当你需要在 setState 中基于"之前的状态 "(previous state)来计算"新的状态 "时,不要直接读取 this.state(或 React Hooks 中的 state 变量),而应该使用 setState 的函数式更新形式(回调形式)。
为什么会有这个报错?
在 React 中,setState 是异步的(在 React 18 及并发模式下更是如此,更新可能会被批量处理或中断)。
如果你直接读取当前的 state 来计算新值,可能会遇到以下问题:
- 竞态条件(Race Condition) :如果你在极短时间内连续调用多次
setState,或者在事件回调、定时器中调用,this.state可能还没有更新到最新值。这会导致你基于一个"过时"的状态进行计算,从而丢失更新。 - 数据不一致:最终渲染的状态可能不是你预期的累加结果。
错误的写法
javascript
// Class Component 示例
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
// 错误:直接引用了 this.state
// 如果此时 setState 还没执行完,this.state.count 还是旧值
this.setState({
count: this.state.count + 1
});
};
render() {
return <div>{this.state.count}</div>;
}
}
React 为了性能,不会每次调用 setState 都立即更新界面和 this.state,而是会把多次更新批量处理 (Batching)。
第 1 次点击:
代码执行:读取 this.state.count。此时内存里还是 0。
计算:0 + 1 = 1。
任务队列:放入任务 { count: 1 }。
注意:此时 this.state.count 仍然是 0,还没变!
第 2 次点击(紧接着发生,第 1 次还没处理完):
代码执行:读取 this.state.count。因为还没更新,它依然是 0!
计算:0 + 1 = 1。
任务队列:放入任务 { count: 1 }。
第 3 次点击:
代码执行:读取 this.state.count。依然是 0!
计算:0 + 1 = 1。
任务队列:放入任务 { count: 1 }。
React 开始处理队列:
React 合并这些更新。由于你传入的都是对象 { count: 1 },后面的会覆盖前面的,或者它们基于同一个旧值计算。
最终结果:count 变成了 1。
预期结果:应该是 3。
正确写法
javascript
this.setState((prevState) => ({ count: prevState.count + 1 }));
-
第 1 次点击:
放入任务:函数
f1(prevState) => prevState.count + 1。 -
第 2 次点击:
放入任务:函数
f2(prevState) => prevState.count + 1。 -
第 3 次点击:
放入任务:函数
f3(prevState) => prevState.count + 1。 -
React 开始处理队列(串行执行):
- 执行
f1:传入当前状态 0 。返回0 + 1 = 1。状态更新为 1。 - 执行
f2:传入最新的状态 1 (React 会自动把上一步的结果传进来)。返回1 + 1 = 2。状态更新为 2。 - 执行
f3:传入最新的状态 2 。返回2 + 1 = 3。状态更新为 3。
- 执行
最终结果 :count 变成了 3。符合预期!
no-useless-constructor
在 React 的类组件中,如果你的构造函数 中除了调用 super(props) 之外没有其他逻辑 (例如没有初始化 state 或没有绑定 this ),那么整个构造函数是可以删除的。
javascript
class operateWith extends Component {
constructor(props) {
super(props);
}
}
如果不写构造函数,React 类组件会有一个默认 的构造函数,它会自动调用
super(props)。
不能删除情况
javascript
constructor(props) {
super(props);
this.state = { count: 0 }; // 必须保留
}
现代写法建议
javascript
class operateWith extends Component {
// 直接初始化 state
state = {
count: 0
};
// 自动绑定 this,无需在 constructor 中 bind
handleClick = () => {
console.log(this.state.count);
};
render() {
return <div onClick={this.handleClick}>{this.state.count}</div>;
}
}
no-case-declarations
Unexpected lexical declaration in case block. no-case-declarations
禁止 在 switch 语句的 case 或 default 子句中直接声明变量(使用 let、const、class 或 function)。
为什么会报错?
这是由 JavaScript 的作用域提升 (Hoisting)和switch 语句的特殊执行机制 导致的潜在 Bug。
在 switch 语句中,所有的 case 标签共享同一个块级作用域。如果你在某个 case 中用 let 或 const 声明了一个变量,这个变量在整个 switch 块中都是"存在"的(虽然还没初始化),这会导致以下问题:
- 暂时性死区(TDZ)错误:如果代码执行流跳过了声明该变量的 case(例如进入了另一个 case),但后续代码试图访问该变量,就会抛出 ReferenceError。
- 重复声明错误:如果有多个 case 都声明了同名的变量,会报错。
错误示例
javascript
switch (foo) {
case 1:
let x = 1; // ❌ 报错:Unexpected lexical declaration in case block.
break;
case 2:
console.log(x); // 即使这里没声明 x,但因为作用域共享,x 在这里也是存在的(但在未初始化状态)
break;
}
如何修复?
最标准的修复方法是给每个需要声明变量的 case 添加一个大括号 {},从而创建一个新的块级作用域。
✅ 正确写法
javascript
switch (foo) {
case 1: {
// 添加大括号,创建独立作用域
let x = 1;
console.log(x);
break;
}
case 2: {
// 这里也可以声明同名的 x,不会冲突
let x = 2;
console.log(x);
break;
}
default: {
const y = 'default';
break;
}
}
加上 {} 后,let x 的作用域就被限制在这个大括号内部了。其他的 case 无法访问到这个 x,也不会发生变量提升导致的冲突或暂时性死区问题。
no-return-assign
禁止 在 return 语句中使用赋值表达式
目的是防止开发者在 return 时意外地执行了赋值操作,而不是进行比较或返回预期的值。这通常是因为混淆了 = (赋值) 和 ==/=== (比较)。
为什么要禁止?
🧐 意图不明确(可读性差)
当你看到 return foo = bar + 2; 时,阅读代码的人需要停下来思考:
- 这个函数的真正目的是计算并返回
bar + 2的结果? - 还是为了产生副作用 ,即更新
foo的值? - 或者这只是一个笔误 ,原本想写的是比较运算符
==或===?
这种歧义增加了代码维护的认知负担。
⚠️ 容易掩盖错误
在 JavaScript 中,= (赋值)和 == / === (比较)非常相似。如果在 return 中误用了赋值符号,代码依然能正常运行,不会报错,但逻辑却完全错了。禁止这种写法可以从源头杜绝这类难以察觉的 Bug。
🚫 违反单一职责原则
函数应该专注于做一件事。如果一个函数既负责"修改外部状态(赋值)",又负责"返回结果",这就混淆了命令 (做某事)和查询(获取某事)的界限。
javascript
// ❌ 错误示例:本意是比较,结果变成了赋值并返回 true/false (取决于语言特性,JS中返回赋值后的值)
function check(a, b) {
return a = b; // 报错!应该是 return a === b;
}
ref={node => (this.input = node)}
// ✅ 推荐写法:清晰表明目的是副作用,而非返回值
ref={node => {
this.input = node;
// 不需要 return,或者显式 return undefined
}}
import/extensions
Unexpected use of file extension "js" for "video.js/dist/video.js
ESLint认为在导入语句中显式书写 .js 文件扩展名是不规范的
这条规则旨在保持导入路径的整洁和一致性。通常,模块打包工具 (如 Webpack)可以自动解析不带扩展名的文件,因此 ESLint 建议省略它们。
javascript
import videojs from 'video.js/dist/video.js'; // ❌ ESLint 报错
import videojs from 'video.js/dist/video'; // ✅ 正确
import/no-webpack-loader-syntax
javascript
require('!style-loader!css-loader!video.js/dist/video-js.css'); // ❌ ESLint 报错
import 'video.js/dist/video-js.css'; // ✅ 正确
代码使用了 Webpack 的"内联 Loader 语法"(即 ! 符号),但 ESLint 的规则 import/no-webpack-loader-syntax 禁止了这种做法。
在现代前端开发中,最佳实践 是将 Loader 配置在 webpack.config.js 文件中 ,而不是写在业务代码里。这样可以让代码更干净,且与构建配置解耦。
jsx-a11y/media-has-caption
Media elements such as <audio> and <video> must have a <track> for captions.
核心目的是强制要求为多媒体元素提供字幕,以确保听障人士或无法听到声音的用户也能获取内容信息。
规则要求 <audio> 和 <video> 标签必须包含一个 <track> 子元素,且该 <track> 的 kind 属性必须设置为 "captions"。
- Captions (隐藏式字幕) :不仅包含对话,还包含非语言的声音信息(如
[音乐]、[笑声]、[脚步声]),这对于听障用户至关重要。
html
// 音频元素
<audio controls src="sound.mp3">
<track kind="captions" src="captions.vtt" srcLang="en" label="English Captions" />
</audio>
// 视频元素
<video controls src="movie.mp4">
<track kind="captions" src="captions.vtt" srcLang="zh" label="中文字幕" default />
</video>
| 属性 | 说明 | 示例值 |
|---|---|---|
| kind | 必须项。设置为 captions 以满足无障碍要求。 |
captions |
| src | 字幕文件的路径,通常是 WebVTT (.vtt) 格式。 |
./subs.vtt |
| srcLang | 字幕文本的语言代码。 | zh, en |
| label | 用户可见的轨道标签(用于切换字幕)。 | 中文, English |
| default | (可选) 指定该轨道为默认显示。 | default |
-
captionsvssubtitles:- Subtitles:假设观众能听到声音但听不懂语言(主要用于翻译)。
- Captions :假设观众听不到 声音,因此需要描述背景音、语气和音效(这是
jsx-a11y规则强制要求的)。
关于 <audio> 标签的特殊性:
虽然 HTML 标准允许 <audio> 嵌套 <track>,但浏览器对 <audio> 的原生字幕支持非常有限(通常不显示)。
- 为了满足 ESLint 规则 :你依然需要在代码中写上
<track kind="captions" ... />,这样可以通过静态代码检查。 - 为了实际用户体验 :如果要在音频播放时显示字幕,通常需要使用 JavaScript 库(如 WebVTT 解析器)来监听轨道并动态渲染文本,或者使用
<video>标签来播放纯音频(配合一张静态海报图),因为<video>对字幕的原生支持更好。
class-methods-use-this
在类中定义的 onPanelChange 方法没有使用 this 关键字
javascript
class MyComponent extends React.Component {
// ... 其他代码
onPanelChange(value) {
console.log('面板值改变了:', value);
// 这个方法里没有用到 this
}
// ... 其他代码
}
方法一:将方法改为静态方法
如果 onPanelChange 方法确实不需要访问或修改当前组件实例的任何属性或方法,那么最合适的做法就是将其声明为静态方法。
javascript
class MyComponent extends React.Component {
// ... 其他代码
static onPanelChange(value) { // 加上 static 关键字
console.log('面板值改变了:', value);
}
// ... 其他代码
}
方法二:在规则中排除该方法
在某些特定场景下,比如作为回调函数传递给第三方库的组件时,你可能必须将它定义为实例方法,即使它内部没有使用 this。这时,你可以通过配置 ESLint 规则来忽略这个警告。
你需要修改项目的 ESLint 配置文件(如 .eslintrc.js 或 .eslintrc.json),为 class-methods-use-this 规则添加 exceptMethods 选项
javascript
// .eslintrc.js
module.exports = {
// ... 其他配置
rules: {
"class-methods-use-this": ["error", { "exceptMethods": ["onPanelChange"] }]
}
};
方法三:使用箭头函数
Strings must use singlequote.
字符串必须使用单引号
也可以向下面这样,改为强制使用双引号
javascript
// .eslintrc.js
module.exports = {
rules: {
'quotes': ['error', 'double'] // 强制使用双引号
}
}
Block must not be padded by blank lines.
代码块内部的首尾不应有多余的空行
错误示例
javascript
function login() {
console.log('登录逻辑');
// ...其他代码
}
正确写法
javascript
function login() {
console.log('登录逻辑');
// ...其他代码
}