主题
第 18 章 · 代码质量(ESLint / Prettier / Husky / Commitlint / TypeScript)
一句话开篇:本章你将学会让代码"出厂前自动质检"——格式统一、规则一致、提交前自检、commit 信息规范。
0. 生活类比(先建立直觉)
0.1 ESLint = 校稿员
你写完一篇文章交给出版社,校稿员会逐字检查:
- 错别字(语法错误、未声明变量)
- 风格不统一(双引号 / 单引号、缩进)
- 语病(用
==而非===) - 危险表达(
eval、with)
ESLint 就是这位校稿员,根据团队"出版规范"(.eslintrc)逐行扫描你的代码。
0.2 Prettier = 排版师
校稿员只挑错字,但不会管"段落怎么排"。Prettier 是排版师,不关心你写得对不对,只关心格式好不好看——缩进、换行、引号、分号、行宽,统一一刀切。
0.3 Husky + lint-staged = 保安 + 抽检员
公司大门口有保安(Husky),下班时拦住你检查包里有没有公司文件。但保安不可能检查整栋楼,所以他叫一个抽检员(lint-staged):只检查你今天碰过的东西(git 暂存区里的文件),快又准。
0.4 Commitlint = 公文写作规范员
公司规定所有公文必须按"主送:xxx;事由:xxx;正文:xxx"的格式写。Commitlint 就是这个规范员,看你的 commit message 是不是符合 feat: 新增登录功能 这种格式,不符合直接驳回。
0.5 TypeScript = 严格的语法老师
JS 像一个宽松的同事,"差不多就行";TS 像一个挑剔的语文老师,"主语和谓语搭配不当!介词使用错误!"——在你写代码的时候就把毛病揪出来,不让你蒙混过关。
1. 概念是什么
1.1 代码质量工具全景
| 工具 | 解决什么 | 关键词 |
|---|---|---|
| ESLint | 代码逻辑问题 | 规则、插件 |
| Prettier | 代码格式问题 | 风格统一 |
| TypeScript | 类型错误 | 静态类型 |
| Husky | Git 钩子管理 | pre-commit / commit-msg |
| lint-staged | 只检查暂存区文件 | 增量 |
| Commitlint | commit message 规范 | conventional commits |
1.2 它们如何协作
你写完代码
│
│ git add .
▼
git commit -m "..."
│
├─► Husky 触发 pre-commit 钩子
│ │
│ └─► lint-staged 只对暂存文件做:
│ ├─► Prettier --write (自动格式化)
│ ├─► ESLint --fix (自动修复 + 检查)
│ └─► tsc --noEmit (类型检查)
│ ↓ 任一失败,commit 中断
│
├─► Husky 触发 commit-msg 钩子
│ │
│ └─► Commitlint 校验 message 格式
│ ↓ 失败,commit 中断
│
└─► commit 成功,进入历史2. 为什么需要它
2.1 没有这些工具会怎样?
真实场景:5 人团队,没有任何代码规范
- 张三爱用单引号,李四爱双引号,每次互相 review 改半天
- 王五写了
if (a == 1)引发隐式类型转换 bug - 赵六提交了 console.log 调试代码到生产
- commit 信息全是"修复"、"再提交",三个月后看不懂当时改了什么
- 周日凌晨上线,发现某个变量类型对不上,整个页面白屏
✅ 有了这些工具:
- 团队代码格式 100% 一致
- 99% 的低级错误自动拦截在提交之前
- commit 历史像变更日志一样清晰
- 类型错误在编辑器里就标红,不会等到运行才崩
3. 怎么用(最小示例)
3.1 一键搭建(推荐使用 Vite/Next 模板,自带配置)
bash
# 用 ESLint 官方初始化
npm init @eslint/config@latest
# 装 Prettier 及与 ESLint 协作的包
pnpm add -D prettier eslint-config-prettier
# 装 Husky + lint-staged
pnpm add -D husky lint-staged
npx husky init
# 装 Commitlint
pnpm add -D @commitlint/cli @commitlint/config-conventional
echo "export default {extends:['@commitlint/config-conventional']}" > commitlint.config.js3.2 最简 .eslintrc.cjs
js
module.exports = {
env: { browser: true, es2022: true, node: true },
extends: ['eslint:recommended', 'prettier'],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
rules: {
'no-unused-vars': 'warn',
'no-console': ['warn', { allow: ['error', 'warn'] }],
'eqeqeq': 'error',
},
};3.3 最简 .prettierrc
json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"trailingComma": "all",
"arrowParens": "always"
}4. 进阶要点
4.1 ESLint 详解
配置层级
.eslintrc.cjs / eslint.config.js (Flat Config)
├── env ← 运行环境(浏览器/Node/jest)
├── parser ← 语法解析器(@typescript-eslint/parser)
├── plugins ← 插件提供新规则(react、import、jsx-a11y)
├── extends ← 继承的规则集(eslint:recommended)
├── rules ← 自定义规则覆盖
└── overrides ← 针对特定文件的特殊配置规则等级
js
rules: {
'no-console': 'off', // 0 关闭
'no-console': 'warn', // 1 警告(黄色)
'no-console': 'error', // 2 错误(红色,CI 会失败)
}Flat Config(ESLint 9+ 默认)
js
// eslint.config.js(新格式,更清晰)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
rules: { 'no-unused-vars': 'warn' },
},
];旧的 .eslintrc.cjs 仍然支持但已被标记为 legacy。
4.2 Prettier 与 ESLint 的关系
新手最常见的混淆:它们到底是不是一回事?
| 维度 | ESLint | Prettier |
|---|---|---|
| 目标 | 找代码错误(逻辑+风格) | 统一代码格式 |
| 范围 | 全方位 | 只管格式 |
| 可配置性 | 高(200+ 规则) | 低(故意的,避免争论) |
| 自动修复 | 部分规则可 --fix | 全部 --write |
| 比喻 | 校稿员(找错) | 排版师(重排) |
两者会冲突:例如 ESLint 要求 prefer-const,Prettier 不管这个;但 ESLint 的 quotes 规则可能和 Prettier 的引号设置打架。
✅ 解决方案:装 eslint-config-prettier,关掉所有和 Prettier 冲突的 ESLint 风格规则。
js
// .eslintrc.cjs
extends: [
'eslint:recommended',
'prettier' // 必须放最后!关掉所有冲突的格式规则
]4.3 Husky + lint-staged 工作流
为什么不直接 eslint .?
全量 lint 慢;改了一个文件却扫描全工程,几万行,等 30 秒。
✅ lint-staged 只处理 git 暂存区里的文件——只检查你即将提交的内容,秒级完成。
配置
json
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css}": ["prettier --write"]
}
}bash
# .husky/pre-commit(Husky v9+ 不再需要 #!/usr/bin/env sh)
npx lint-stagedbash
# .husky/commit-msg
npx --no-install commitlint --edit "$1"4.4 Commitlint + Conventional Commits
Conventional Commits 规范
<type>[optional scope]: <description>
[optional body]
[optional footer]type 列表:
| type | 含义 |
|---|---|
feat | 新功能 |
fix | bug 修复 |
docs | 仅文档变更 |
style | 不影响逻辑的格式变化(空格、分号) |
refactor | 重构(既不是修 bug 也不是加功能) |
perf | 性能优化 |
test | 测试相关 |
build | 构建系统 / 依赖变化 |
ci | CI 配置 |
chore | 其他杂项 |
revert | 撤销之前的 commit |
示例:
✅ feat(auth): 新增第三方登录支持
✅ fix(cart): 修复购物车数量为 0 时仍能下单
✅ docs: 更新 README 安装步骤
✅ refactor!: 重构用户模块(! 表示破坏性变更)
❌ 修复一个bug
❌ wip
❌ 提交为什么用 Conventional Commits?
- 自动生成 changelog(配合
standard-version/changesets) - 自动决定版本号(feat → minor、fix → patch、! → major)
- commit 历史可读性强
- 工具链集成(GitHub PR 标题、CI 跳过)
4.5 TypeScript 融入工作流
bash
# 在 lint-staged 里追加类型检查
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write",
"bash -c 'tsc --noEmit'"
]⚠️ 注意:tsc --noEmit 会扫描整个 TS 项目(不能只检查暂存文件),所以建议放在 CI 而非 pre-commit,避免提交太慢。
推荐流程
pre-commit: eslint --fix + prettier --write (秒级)
commit-msg: commitlint (毫秒)
pre-push: tsc --noEmit + 单元测试 (10~30s)
CI: 完整 lint + 类型检查 + 构建 + 测试 (1~5 min)4.6 EditorConfig:编辑器层的最低保障
.editorconfig 是编辑器原生支持的"最低纲领"——告诉 VSCode/IDEA/Sublime 用什么缩进、换行、字符集。
ini
# .editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false任何编辑器一打开文件就生效,比 Prettier 更底层。三者关系:EditorConfig(保底) + Prettier(统一) + ESLint(查错)。
4.7 工具职责清单
┌──────────────────────────────────────────────────┐
│ 职责 │ 谁来做 │
├──────────────────────────────┼───────────────────┤
│ 编辑器原生缩进/换行规则 │ EditorConfig │
│ 代码格式(引号/分号/行宽) │ Prettier │
│ 代码逻辑/风格规则 │ ESLint │
│ 类型检查 │ TypeScript │
│ 提交前自动检查 │ Husky+lint-staged│
│ commit message 规范 │ Commitlint │
│ CI 中再次校验 │ GitHub Actions │
└──────────────────────────────┴───────────────────┘4.8 推荐扩展(可选)
- eslint-plugin-react / eslint-plugin-vue:框架专属规则
- eslint-plugin-import:检查 import 顺序与有效性
- eslint-plugin-jsx-a11y:可访问性检查
- eslint-plugin-unicorn:现代 JS 最佳实践
- eslint-plugin-tailwindcss:Tailwind class 检查
- standard-version / release-it:自动 release + changelog
5. 实战案例
详见 examples/ 目录:
.eslintrc.cjs— ESLint 配置示例eslint.config.js— Flat Config 新格式示例.prettierrc— Prettier 配置commitlint.config.js— Commitlint 配置.husky/pre-commit— Husky pre-commit 钩子
6. ⚠️ 易踩的坑
坑 1:ESLint 和 Prettier 互相冲突
ESLint 报错:Strings must use doublequote
Prettier 自动改成单引号
→ 永远修不完✅ 装 eslint-config-prettier 并放在 extends 数组最后。
坑 2:Husky 配好但同事克隆后没生效
Husky v9+ 默认依赖 prepare 脚本:
json
{
"scripts": {
"prepare": "husky" // 装依赖时自动跑
}
}漏写就要让同事手动 npx husky init,不可靠。
坑 3:lint-staged 把 git 状态搞乱
如果 ESLint --fix 后改了文件,但你忘了 git add,会出现"提交了未修复的版本"。
✅ lint-staged 会自动重新 add 修改过的文件,所以装一定要装最新版。
坑 4:commit-msg 钩子在 IDE 提交时不触发
部分 IDE(旧版 VSCode、JetBrains)跳过 git hooks。
✅ 设置 git config core.hooksPath .husky 显式指定钩子目录;新 IDE 已修复。
坑 5:no-unused-vars 误报 TypeScript 类型
ts
// ESLint 报"User 未使用"
import type { User } from './types';
function f(): User { ... } // 实际用了✅ 用 @typescript-eslint/no-unused-vars 替代原生规则。
坑 6:rules 写错值
js
// ❌ 错误:不会报错但规则也不生效
'no-console': 'warning' // 应为 'warn'
'no-console': 1 // 1 也可以但 'warn' 更清晰坑 7:Prettier 配错文件名
.prettierrc ← JSON
.prettierrc.json ← JSON
.prettierrc.js ← JS
prettier.config.js ← JS
package.json 里 "prettier": {...} ← 也行多个同时存在时优先级要查文档,最佳实践:项目里只放一个。
坑 8:commitlint 卡住 emergency 修复
凌晨 3 点要紧急上线 bug 修复,但 commit 不规范被卡住。
✅ 应急:git commit --no-verify -m "hotfix" 跳过钩子。但这是应急,不能成习惯。
7. 最佳实践 Checklist
- [ ] 项目根目录有
.editorconfig、.prettierrc、.eslintrc/eslint.config.js三件套 - [ ]
extends数组里prettier配置放在最后 - [ ]
package.json含"prepare": "husky"自动启用 - [ ] 配置
lint-staged:JS/TS 跑 ESLint+Prettier,CSS/MD 只跑 Prettier - [ ] commit-msg 钩子接入 commitlint,扩展
@commitlint/config-conventional - [ ] CI 中独立跑一次
eslint .+tsc --noEmit+prettier --check . - [ ] VSCode 装上 ESLint + Prettier 插件,配置
editor.formatOnSave: true - [ ] TypeScript 项目使用
@typescript-eslint/*系列插件 - [ ] React 项目加
eslint-plugin-react-hooks(Hooks 调用规则强制) - [ ] commit 历史使用
feat:/fix:/chore:等规范前缀 - [ ] 配合
changesets或standard-version自动生成 CHANGELOG - [ ] 紧急情况下知道
--no-verify但不滥用
8. 一句话总结
代码质量工具链就是出厂质检流水线:Prettier 统一外观 → ESLint 检查内在 → TypeScript 把控类型 → Husky 把守关卡 → Commitlint 规范档案——每一道环节自动化,团队代码自然又齐又稳。