使用 eslint-plugin-unicorn 提升 JavaScript 代码质量
适用于 ESLint 10 的 eslint-plugin-unicorn:使用 flat config 安装,选择 recommended 或 unopinionated 预设,并用可自动修复的规则提升 JS 和 TS 质量。
eslint-plugin-unicorn 是一个规则数量庞大、具有强烈主观倾向的 ESLint 插件——拥有 300 余条规则——能够推动现代 JavaScript 和 TypeScript 编程范式的落地,并捕获细微的代码质量缺陷,且其中大多数规则支持自动修复。该插件要求 ESLint >=10.4、flat config 配置格式以及 ESM 模块系统,这意味着旧版 .eslintrc 加 extends: 'plugin:unicorn/recommended' 的配置方式已不再适用。如果你已经在使用基础 ESLint 配置,并希望在不编写自定义规则的前提下提升代码质量,这个插件是最快捷的途径。
需要注意的是,“强主观倾向”是把双刃剑。部分规则能有效防止意外的二次方复杂度循环和真实的正确性缺陷;另一些则强制推行某些风格偏好,可能与已有代码库产生冲突。本指南将两者加以区分:插件的具体约束内容、哪些规则物有所值、哪些应降级或禁用,以及一份可直接复制使用的 eslint.config.mjs,适配当前主流工具链。
核心要点
- eslint-plugin-unicorn 提供 300 余条规则,且大多数支持自动修复。自 v69(2026 年 6 月)起,要求 ESLint ≥10.4、flat config 配置格式以及 ESM——
.eslintrc已不再有效,因为 ESLint 10 已彻底移除该遗留系统。 - 最简采用路径:继承
unicorn/recommended,运行eslint --fix,然后关闭少数与代码库冲突的规则——或者直接从unopinionated预设出发,该预设已预先禁用了那些主观性较强的规则。 - 插件导出三个预设:
recommended、unopinionated(即 recommended 去除主观性规则后的版本)以及all。 prefer-set-has能将循环中意外产生二次方复杂度的.includes()查找转换为 O(n) 的Set.has()检查;no-array-for-each则将.forEach()改写为for…of,以获得更好的 TypeScript 类型收窄效果。- 可将其与
@typescript-eslint/recommended-type-checked配合使用,将编程范式约束与类型感知 lint 检查结合起来。
eslint-plugin-unicorn 是什么,以及它的设计理念
eslint-plugin-unicorn 是一套 ESLint 规则集合,引导代码向更现代、更不易出错的 JavaScript 和 TypeScript 模式靠拢——例如优先使用 Set 成员检查而非数组扫描、用 for…of 替代 .forEach()、使用 node: 导入说明符替代裸模块名称等。它要求 ESLint >=10.4、flat config 以及 ESM。该仓库自述提供 300 余条规则,最新版本为 69.0.0,大约每月发布一次。
有两个设计特点决定了你的采用方式。第一,大多数规则支持自动修复,因此大量违规可通过 eslint --fix 解决,而无需手动修改。第二,插件提供预设配置,无需逐条启用规则。插件导出一个 recommended 配置用于强制推行最佳实践,另外还有一个 all 配置用于启用所有非废弃规则。规则列表使用 emoji 标注状态:✅ 表示该规则已在 recommended 配置中启用,☑️ 表示已在 unopinionated 配置中启用,🔧 表示可通过 --fix CLI 选项自动修复。
适用范围主要是 JavaScript 和 TypeScript,同时有一个实用的补充说明:虽然大多数规则针对 JavaScript 和 TypeScript,但部分规则在配合相应 ESLint 语言插件(如 @eslint/css、@eslint/json、@eslint/markdown 或 @html-eslint/eslint-plugin)使用时,也可对其他文件类型进行 lint 检查。对大多数团队而言,安装该插件的主要原因仍是其 JS/TS 规则。
在 flat config 与 ESM 环境下安装 eslint-plugin-unicorn
Discover how at OpenReplay.com.
安装插件及兼容版本的 ESLint,然后在 flat config 格式的 eslint.config.mjs 中继承 recommended 预设。当前要求使用 ESLint 10.x——ESLint 10 已彻底移除遗留的 eslintrc 系统,flat config 成为唯一支持的格式,因此任何展示 .eslintrc.json 加 extends: 'plugin:unicorn/recommended' 的教程均已过时。ESLint 要求使用当前受支持的 Node.js 运行时(根据 ESLint npm 页面,需 Node 20.19+、22.13+ 或 24+)。
npm install --save-dev eslint eslint-plugin-unicorn
一份仅针对 JavaScript 和 TypeScript 文件的最小可用配置:
// eslint.config.mjs
import unicorn from 'eslint-plugin-unicorn';
import { defineConfig } from 'eslint/config';
export default defineConfig([
{
files: ['**/*.{js,mjs,cjs,ts,tsx}'],
extends: [unicorn.configs.recommended],
rules: {
// 在此处添加覆盖规则
},
},
]);
这与官方用法一致:从 ‘eslint-plugin-unicorn’ 导入 unicorn,从 ‘eslint/config’ 导入 {defineConfig},然后在 defineConfig 中设置 files 和 extends: [unicorn.configs.recommended]。使用 files 进行作用域限定非常重要——当你在同一配置中对非 JavaScript 语言进行 lint 时,应将 Unicorn 的 JS 规则放在独立的配置对象中。
可从以下三个预设中选择起点:
recommended— 精心策划的默认预设。unopinionated— recommended 去除主观性规则后的版本。all— 所有非废弃规则。
如果团队觉得主观性规则过于干扰,可将 unicorn.configs.recommended 替换为 unicorn.configs.unopinionated,从而跳过下文大部分手动调整工作。
迁移说明:如果你仍依赖某个只提供 eslintrc 风格配置的插件,可使用 @eslint/eslintrc 中的 FlatCompat 进行包装。在 ESLint 10.x 环境下,请使用最新版 @eslint/eslintrc。
eslint-plugin-unicorn 中价值最高的规则及修复前后对比
价值最高的 Unicorn 规则值得优先采用,因为每条规则都能防止性能、正确性或可读性方面的问题,而非仅仅强制推行风格偏好。以下所有示例均为 eslint --fix 的输出结果,而非手工编写的理想代码。
prefer-set-has — 消除意外的二次方复杂度查找
prefer-set-has 会标记仅用于 .includes() 成员检查的数组,并引导你改用 Set。它在检查元素是否存在时,优先使用 Set#has() 而非 Array#includes()。这在循环内部尤为重要:Array.prototype.includes 的时间复杂度为 O(n),因此在遍历另一个集合的每次迭代中调用它,整体复杂度将达到 O(n²);而 Set.prototype.has 的时间复杂度近似为常数级。
// ❌ O(n²):includes() 在每次迭代中都会重新扫描整个数组
const allowed = ['read', 'write', 'admin', /* …数百个… */];
const granted = requested.filter((scope) => allowed.includes(scope));
// ✅ O(n):Set.has() 每次查找的时间复杂度近似为常数级
const allowed = new Set(['read', 'write', 'admin', /* …数百个… */]);
const granted = requested.filter((scope) => allowed.has(scope));
这类问题的失效模式是隐蔽的:在小数据量下运行正常,随着数据增长性能急剧下降。这类问题往往会演变成你在生产环境中才开始排查的慢交互回归,而非在代码审查阶段发现。
no-array-for-each — 更好的 TypeScript 类型收窄与控制流
no-array-for-each 将 .forEach() 回调改写为 for…of 循环,优先使用 for…of 而非 forEach 方法。其收益不仅限于风格层面:for…of 能在整个循环体中提供更好的 TypeScript 类型收窄,允许直接使用 await 而无需回调嵌套,并支持 break/continue——这些在 .forEach() 回调中均无法简洁实现。
// ❌
users.forEach((user) => {
sendEmail(user);
});
// ✅ 自动修复后
for (const user of users) {
sendEmail(user);
}
prefer-node-protocol — 明确标识 Node 内置模块
prefer-node-protocol 将裸内置模块导入改写为显式的 node: 前缀形式,优先在导入 Node.js 内置模块时使用 node: 协议。显式前缀使导入来源明确无歧义,避免被同名 npm 包遮蔽。
// ❌
import fs from 'fs';
import { join } from 'path';
// ✅ 自动修复后
import fs from 'node:fs';
import { join } from 'node:path';
更多可自动修复的实用规则
以下规则均将旧式写法映射为现代等价形式,并可自动修复:
// throw-new-error — 抛出错误时要求使用 new
throw Error('boom'); // ❌
throw new Error('boom'); // ✅
// prefer-string-replace-all — 全局替换时无需 /g 正则
str.replace(/-/g, ' '); // ❌
str.replaceAll('-', ' '); // ✅
// prefer-array-flat-map — 合并 map().flat()
rows.map((r) => r.cells).flat(); // ❌
rows.flatMap((r) => r.cells); // ✅
// new-for-builtins — 要求使用 new 创建 Map/Set/Array/Date 等
const cache = Map(); // ❌
const cache = new Map(); // ✅
throw-new-error 规则要求在创建错误时使用 new(同时覆盖 const e = Error() 和 throw Error() 两种写法),该规则已包含在 recommended 配置中且支持自动修复。new-for-builtins 强制对所有内置类型使用 new,但 String、Number、Boolean、Symbol 和 BigInt 除外。Array.prototype.flatMap 不仅语义更清晰,还避免了 .map().flat() 产生的中间数组内存分配。
另有两条注重可读性的规则也属于高价值规则集。better-regex 通过缩短并统一正则表达式来提升其质量(例如将 [0-9] 改写为 \d)。prefer-ternary 将简单的 if/else 块折叠为三元表达式:它优先使用三元表达式而非仅用于返回或赋值的简单 if 语句。if-else 语句通常比单行三元表达式占用更多行数,并且可能迫使你用 let 声明变量仅为了在分支中重新赋值——这会使变量不必要地保持可变状态,同时导致 prefer-const 无法对该变量发出警告。
// ❌
function status(ok) {
if (ok) {
return 'pass';
} else {
return 'fail';
}
}
// ✅ 自动修复后
function status(ok) {
return ok ? 'pass' : 'fail';
}
争议性规则及决策建议
部分 recommended 规则强制推行的是偏好而非正确性,在已有代码库上往往产生最多噪音。下表列出了常见规则的保留/警告/禁用建议,这些规则均在 recommended 中默认启用。
| 规则 | 建议 | 原因 |
|---|---|---|
prevent-abbreviations | 设为警告或限定作用域 | 会将 e 重命名为 error、req 重命名为 request、props 重命名为 properties,与框架和团队已有的命名习惯冲突。 |
no-null | 如使用 null 则禁用 | Unicorn 偏好 undefined,但 ORM 或 API 返回 null 是合理场景——没有统一的正确答案。 |
no-array-reduce | 保留或设为警告 | 禁止使用 Array#reduce() 和 Array#reduceRight();从可读性角度看有一定道理,但对许多团队而言 reduce 是完全合理的用法。 |
filename-case | 限定忽略范围 | 与框架强制要求的文件名命名规范冲突;建议保留该规则但排除例外情况。 |
no-null 的情况最为典型:Unicorn 偏好 undefined,但许多数据库驱动和 HTTP API 返回的就是 null,强制统一只会带来无谓的转换噪音,对正确性毫无帮助。当 null 是你数据契约的一部分时,直接禁用该规则即可。
这些规则无需手动逐一禁用。unopinionated 预设默认关闭了主观性规则——包括 no-null 和 prevent-abbreviations——因此它是介于 recommended 和高度定制化配置之间的内置折中方案。
对于你选择保留但需要调整的规则,优先考虑限定作用域而非直接禁用。以下是针对 filename-case 排除框架文件的常见模式:
{
files: ['**/*.{js,ts,tsx}'],
extends: [unicorn.configs.recommended],
rules: {
'unicorn/no-null': 'off',
'unicorn/prevent-abbreviations': 'warn',
'unicorn/filename-case': [
'error',
{
cases: { pascalCase: true, camelCase: true },
ignore: ['next-env\\.d\\.ts', 'vite\\.config\\.ts'],
},
],
},
}
在已有代码库上渐进式采用
分阶段推出插件,避免首次运行就被大量错误淹没。以下是经过验证的步骤:
- 从预设出发。 继承
unicorn/recommended,或者如果主观性规则对团队干扰过大,则使用unicorn/unopinionated。 - 优先执行自动修复。 运行
npx eslint . --fix。由于大多数规则支持自动修复,大量违规将在此步骤消失,你只需审查一次机械性的 diff,而非数百条内联错误。 - 将噪音较大的规则降级为警告。 对于仍在大量触发但不涉及正确性问题的规则——
prevent-abbreviations是最常见的情况——将其设为'warn',使其在迁移期间不阻塞 CI。 - 限定忽略范围而非直接禁用。 使用
ignore选项(如filename-case的用法)和filesglob 模式来划定例外,而非在全局范围内关闭有用的规则。 - 叠加类型感知 lint。 在 Unicorn 的基础上添加
@typescript-eslint/recommended-type-checked。Unicorn 负责强制推行编程范式;typescript-eslint 的类型检查配置则能捕获只有类型系统才能发现的缺陷。两者相辅相成,对 TypeScript 项目而言同时启用是一个强力的默认选择。
将配置视为一份持续演进的文档。随着插件升级——大约每月发布一次——重新检查规则列表,关注新增规则和重命名情况,并重新评估哪些 warn 级规则已准备好提升为 error。
使用 eslint-plugin-unicorn 提升 JavaScript 代码质量的最快路径并不华丽:继承 unicorn/recommended,运行 eslint --fix,然后将注意力集中在少数真正与团队编码习惯冲突的主观性规则上。今天就从预设开始,让自动修复器完成大部分工作,再从那里逐步调整——官方规则列表是查阅每条规则当前状态和选项的权威参考。
常见问题
eslint-plugin-unicorn 能与 typescript-eslint 协同工作吗,还是会产生冲突?
两者相辅相成,设计上即支持同时运行。Unicorn 负责强制推行现代 JavaScript 和 TypeScript 编程范式,而 typescript-eslint 则添加类型感知规则,捕获只有类型系统才能发现的缺陷。在 flat eslint.config.mjs 中将两者作为独立的配置对象添加,并将 unicorn/recommended 与 typescript-eslint 的 recommended-type-checked 配置配对使用。同时启用两者是 TypeScript 项目的强力默认选择,且不存在需要解决的规则重叠问题。
recommended 预设与 unopinionated 预设有何区别?
recommended 预设是精心策划的默认配置,同时启用正确性规则和主观性风格规则。unopinionated 预设是 recommended 去除主观性规则后的版本,预先禁用了 no-null 和 prevent-abbreviations 等与已有代码库冲突的争议性条目。当团队觉得风格规则干扰过大时,选择 unopinionated;它是介于 recommended 和高度定制化配置之间的内置折中方案。第三个预设 all 则启用所有非废弃规则。
为什么我的旧版 .eslintrc 配置加上 extends plugin:unicorn/recommended 不再生效?
ESLint 10 已彻底移除遗留的 eslintrc 系统,flat config 成为唯一支持的格式,而 eslint-plugin-unicorn 要求 ESLint 10.4 或更高版本、flat config 以及 ESM。旧版 .eslintrc 加 extends 'plugin:unicorn/recommended' 字符串语法已无法加载。请迁移至 eslint.config.mjs,从 'eslint-plugin-unicorn' 导入 unicorn,并在通过 files 限定作用域的配置对象中将 extends 设置为 unicorn.configs.recommended。
添加 eslint-plugin-unicorn 会导致构建因数百个错误而中断吗?
由于大多数 unicorn 规则支持自动修复,大量违规可自动解决。首先带 fix 标志运行 eslint,这将以单次机械性 diff 的形式清除大量问题,而非产生数百条内联错误。对于仍在大量触发但不涉及正确性问题的规则(如 prevent-abbreviations),将其设为 warn,使其在迁移期间不阻塞 CI。这种分阶段推出的方式能在采用插件的同时保持构建绿色通过。