12k
All articles

使用 eslint-plugin-unicorn 提升 JavaScript 代码质量

适用于 ESLint 10 的 eslint-plugin-unicorn:使用 flat config 安装,选择 recommended 或 unopinionated 预设,并用可自动修复的规则提升 JS 和 TS 质量。

OpenReplay Team
OpenReplay Team
使用 eslint-plugin-unicorn 提升 JavaScript 代码质量

eslint-plugin-unicorn 是一个规则数量庞大、具有强烈主观倾向的 ESLint 插件——拥有 300 余条规则——能够推动现代 JavaScript 和 TypeScript 编程范式的落地,并捕获细微的代码质量缺陷,且其中大多数规则支持自动修复。该插件要求 ESLint >=10.4、flat config 配置格式以及 ESM 模块系统,这意味着旧版 .eslintrcextends: '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 预设出发,该预设已预先禁用了那些主观性较强的规则。
  • 插件导出三个预设:recommendedunopinionated(即 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

安装插件及兼容版本的 ESLint,然后在 flat config 格式的 eslint.config.mjs 中继承 recommended 预设。当前要求使用 ESLint 10.x——ESLint 10 已彻底移除遗留的 eslintrc 系统,flat config 成为唯一支持的格式,因此任何展示 .eslintrc.jsonextends: '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 中设置 filesextends: [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 重命名为 errorreq 重命名为 requestprops 重命名为 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-nullprevent-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'],
      },
    ],
  },
}

在已有代码库上渐进式采用

分阶段推出插件,避免首次运行就被大量错误淹没。以下是经过验证的步骤:

  1. 从预设出发。 继承 unicorn/recommended,或者如果主观性规则对团队干扰过大,则使用 unicorn/unopinionated
  2. 优先执行自动修复。 运行 npx eslint . --fix。由于大多数规则支持自动修复,大量违规将在此步骤消失,你只需审查一次机械性的 diff,而非数百条内联错误。
  3. 将噪音较大的规则降级为警告。 对于仍在大量触发但不涉及正确性问题的规则——prevent-abbreviations 是最常见的情况——将其设为 'warn',使其在迁移期间不阻塞 CI。
  4. 限定忽略范围而非直接禁用。 使用 ignore 选项(如 filename-case 的用法)和 files glob 模式来划定例外,而非在全局范围内关闭有用的规则。
  5. 叠加类型感知 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。这种分阶段推出的方式能在采用插件的同时保持构建绿色通过。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.