主题
第 18 章 · 面试题集(代码质量)
Q1:ESLint 和 Prettier 有什么区别?为什么要一起用?
考察点:基础概念区分
标准答案:
| 维度 | ESLint | Prettier |
|---|---|---|
| 目标 | 找代码错误和逻辑/风格问题 | 统一代码格式 |
| 范围 | 全方位(200+ 规则、可定制) | 只管格式,故意少配置 |
| 示例 | === 而非 ==、未声明变量 | 缩进、引号、分号、行宽 |
| 自动修复 | 部分规则可 --fix | 全部 --write |
| 比喻 | 校稿员(找错别字、语病) | 排版师(重排格式) |
为什么一起用:两者关注点不同——ESLint 关心"对不对",Prettier 关心"好不好看"。配合 eslint-config-prettier 关闭与 Prettier 冲突的格式规则,分工明确。
通俗理解:
写论文时,校稿员揪你"用词不当、引用格式错";排版师只管"段落对齐、字体统一"。一个查内容、一个排版面。
代码示例:
js
// .eslintrc.cjs
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier', // ⭐ 必须放最后,关闭所有格式相关规则
],
rules: {
'no-console': 'warn',
'eqeqeq': 'error',
}
};
// .prettierrc
{
"semi": true,
"singleQuote": true,
"printWidth": 100
}加分回答:
- ESLint 早期想做格式统一,但社区争论太大(分号党 vs 无分号党),后来 Prettier 出现接管格式
- Prettier 故意少配置(约 30 个)就是为了"避免争论"
- VSCode 推荐:
editor.defaultFormatter: esbenp.prettier-vscode+editor.formatOnSave: true - CI 中可以只跑
prettier --check .(不修改、只校验)
追问:
eslint-plugin-prettier和eslint-config-prettier区别?- 为什么社区现在不推荐用
eslint-plugin-prettier?
Q2:什么是 Husky?它是怎么工作的?
考察点:Git Hooks 与自动化
标准答案:
Husky 是一个简化 Git Hooks 配置的工具。Git 自带 hook 机制(.git/hooks/ 目录),但默认不会随项目分发。Husky 把钩子放到项目里(.husky/ 目录)并自动安装到 .git/hooks/,确保每个克隆的人都自动启用。
常用钩子:
pre-commit:提交前执行(一般跑 lint-staged)commit-msg:commit message 校验(一般跑 commitlint)pre-push:推送前执行(一般跑测试)
通俗理解:
公司大门保安。你走出大门(git commit)前,保安拦下检查包(lint),不合格不让走。Husky 就是雇这个保安的中介公司——自动给每个新员工(新克隆者)安排好。
代码示例:
bash
# 安装
pnpm add -D husky
npx husky init # 自动设置 prepare 脚本 + 创建 .husky/pre-commit
# .husky/pre-commit
npx lint-staged
# .husky/commit-msg
npx --no-install commitlint --edit "$1"json
// package.json
{
"scripts": {
"prepare": "husky" // 安装依赖时自动启用钩子
}
}加分回答:
- Husky v9 简化了语法(不再需要
#!/bin/sh和. "$(dirname -- "$0")/_/husky.sh") - 通过
git config core.hooksPath .husky让 Git 去 .husky 目录找钩子 - 钩子可以用
--no-verify跳过(应急用) - 与 lint-staged 配合是"标配"
- 在 monorepo 里只在根目录配 husky 即可
追问:
- 为什么 git 自带钩子不能直接用?
- 同事克隆后 husky 不生效怎么排查?
Q3:lint-staged 解决了什么问题?
考察点:性能与增量校验
标准答案:
直接 eslint . 会扫描整个项目,几万行代码十几秒——每次提交都等这么久不可接受。
lint-staged 的核心:只对 git 暂存区里的文件做检查。
工作目录改了 100 个文件
git add 了其中 5 个 (暂存区)
git commit
↓ 触发 pre-commit
lint-staged 只检查这 5 个 ← 秒级完成通俗理解:
机场安检。不可能每个登机的人都被翻箱倒柜检查行李,抽检员只看你拿在手上的随身物品(暂存区文件),快又准。
代码示例:
json
// package.json
{
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yaml,yml}": ["prettier --write"],
"*.{css,scss}": ["stylelint --fix", "prettier --write"]
}
}加分回答:
- lint-staged 自动处理"修复后重新 git add"的逻辑
- 支持 glob、函数返回命令、并行执行
- 与 Husky 是绑定关系,但也能配合其他 git hook 工具
- 大型项目中能把提交时间从几十秒压到几百毫秒
- 钩子失败时整个 commit 中止,强制开发者修复
追问:
- lint-staged 怎么处理"我改了 A 文件,但 lint 时报 B 文件错"?
- 为什么有些命令在 lint-staged 里要包一层 bash -c?
Q4:Conventional Commits 是什么?为什么要遵守?
考察点:commit 规范与工程化
标准答案:
Conventional Commits 是一种 commit message 写作规范:
<type>[optional scope]: <description>
[optional body]
[optional footer]type 列表:feat / fix / docs / style / refactor / perf / test / build / ci / chore / revert
示例:
feat(auth): 新增第三方登录
fix(cart): 修复购物车数量为 0 时仍能下单
docs: 更新 README 安装步骤
refactor!: 重构用户模块(! 表示破坏性变更)为什么要遵守:
- 自动生成 changelog(standard-version、changesets 工具能直接产出)
- 自动决定版本号(feat → minor、fix → patch、! → major)
- 历史可读:搜
feat(auth)直接看到所有 auth 相关功能新增 - CI 集成:可根据 type 跳过/触发不同流水线
- PR Title 规范化:GitHub 上配合 squash merge 一目了然
通俗理解:
公文写作格式。如果所有人写邮件都用"主送:xxx;事由:xxx"格式,你扫一眼就知道这封邮件干啥的,几年后翻历史也能秒懂。
代码示例:
js
// commitlint.config.js
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'subject-case': [0], // 关闭"subject 必须小写"的限制(中文友好)
'header-max-length': [2, 'always', 100],
}
};bash
# .husky/commit-msg
npx --no-install commitlint --edit "$1"加分回答:
- 配合
commitizen(cz-cli) 提供交互式 commit 引导 semantic-release完全自动化:commit → 版本号 → changelog → npm publish → GitHub release- Angular 团队是这套规范的主要推动者
- type 后加
!或 footer 写BREAKING CHANGE:都表示破坏性变更 - monorepo 里 scope 一般用包名(
feat(@my/ui): ...)
追问:
- 怎么让团队自然接受这个规范?
- 已经有几百个不规范 commit,怎么处理?
Q5:什么是 Flat Config?为什么 ESLint 9 默认采用?
考察点:ESLint 现代化
标准答案:
Flat Config(eslint.config.js)是 ESLint 8.21+ 推出、9.0+ 成为默认的新一代配置格式。相比旧的 .eslintrc.* 有四大优势:
- 结构扁平:用数组按顺序合并,不再有
extends字符串魔法 - 真正的 JS 模块:用
import直接引入插件,而不是字符串名(如'plugin:react/recommended') - 可预测:配置合并逻辑更清晰,调试容易
- 现代化:天然支持 ESM、
async/await、动态配置
通俗理解:
旧 .eslintrc.json 像"按一堆开关,每个开关之间关系靠看说明书猜";Flat Config 像"用乐高积木自己拼装控制板,每块积木做什么一眼看清"。
代码示例:
js
// eslint.config.js(新格式)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
plugins: { react },
rules: { 'no-console': 'warn' },
},
prettier, // 放最后关闭格式规则
];js
// 对比旧格式 .eslintrc.cjs
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'prettier'
],
// 等等
};加分回答:
- 旧
.eslintrc已被标记为 legacy,但仍可用至 ESLint 10 - Flat Config 没有
env字段,改用languageOptions.globals - 不再支持
.eslintignore,改在配置里写ignores - TypeScript 用
typescript-eslint包(不是@typescript-eslint/*各包分开装) - 大项目迁移可能需要
eslint-config-flat-gitignore等辅助包
追问:
- 你怎么把一个老项目从 .eslintrc 迁移到 Flat Config?
- Flat Config 怎么针对不同目录设置不同规则?
Q6:TypeScript 类型检查能放进 pre-commit 吗?
考察点:工程化权衡
标准答案:
可以但不推荐放进 pre-commit。原因:
tsc --noEmit必须扫整个项目(不能只检查暂存文件,因为 TS 类型有跨文件依赖)- 中型项目 tsc 一次要 5~30 秒,每次 commit 都等很烦
- 与 lint-staged 的"快速增量"哲学相反
推荐做法:
pre-commit: eslint --fix + prettier --write (秒级)
pre-push: tsc --noEmit + 单元测试 (10~30s)
CI: 完整类型检查 + 构建 + 测试 (1~5 min)通俗理解:
提交是"频繁的小事"(一天几十次),所以要快;推送是"阶段性确认"(一天几次),可以慢点;CI 是"对外承诺前的最终检查",慢但全。
代码示例:
bash
# .husky/pre-push
echo "🔍 类型检查..."
npx tsc --noEmit || exit 1
echo "🧪 跑测试..."
npx vitest run --changed HEAD~1 || exit 1yaml
# GitHub Actions CI
- run: pnpm install
- run: pnpm tsc --noEmit
- run: pnpm test
- run: pnpm build加分回答:
- 用
tsc --build --watch+--incremental配合.tsbuildinfo缓存可加速 - VSCode 的 TS Server 已经做了实时类型检查,pre-commit 阶段重复不划算
- 大型 monorepo 用
tsc --build增量构建项目引用(project references) - IDE + CI 双保险足以拦截 99% 的类型错误
追问:
- 怎么针对单个文件做类型检查?
- pre-push 失败时怎么强制推?
Q7:编辑器格式化设置混乱怎么办?
考察点:团队协作规范
标准答案:
层层兜底:
.editorconfig:编辑器原生支持,告诉 VSCode/IDEA/Sublime 缩进、字符集、换行符- VSCode 项目级配置(
.vscode/settings.json):约定保存时格式化、默认 formatter - VSCode 推荐扩展(
.vscode/extensions.json):让新人 clone 项目时弹窗提示装 ESLint/Prettier - Prettier 配置:
.prettierrc - Husky pre-commit:最终防线,提交前强制格式化
通俗理解:
像"装修标准规范":
- 国家标准(EditorConfig)大家都守
- 地方标准(VSCode 设置)项目内统一
- 工种标准(Prettier)专业人员严格遵守
- 出厂检验(Husky)不合格不让出门
代码示例:
json
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}json
// .vscode/extensions.json
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig"
]
}加分回答:
.editorconfig是跨编辑器标准(W3C 推荐),最低保障- monorepo 里所有子包共用根目录的配置即可
- 通过 GitHub PR template 提醒贡献者格式
- 用
pre-commitframework 替代 husky 也是选项(Python/Go 项目常用)
追问:
- 同事在 IDEA 里写代码,怎么保证和 VSCode 一致?
- 远程开发(codespaces)怎么处理这套配置?
Q8:能否分享一套从 0 搭建的代码质量工具链命令?
考察点:实战搭建能力
标准答案:
bash
# 1. 项目初始化
mkdir my-project && cd my-project
pnpm init
git init
# 2. ESLint
pnpm add -D eslint typescript-eslint @eslint/js
npx eslint --init # 或手写 eslint.config.js
# 3. Prettier
pnpm add -D prettier eslint-config-prettier
echo '{"semi":true,"singleQuote":true,"printWidth":100}' > .prettierrc
# 4. Husky + lint-staged
pnpm add -D husky lint-staged
npx husky init
echo "npx lint-staged" > .husky/pre-commit
# 5. Commitlint
pnpm add -D @commitlint/cli @commitlint/config-conventional
echo "export default {extends:['@commitlint/config-conventional']}" > commitlint.config.js
echo 'npx --no-install commitlint --edit "$1"' > .husky/commit-msg
# 6. lint-staged 配置写到 package.json
# 7. EditorConfig
cat > .editorconfig << 'EOF'
root = true
[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
EOF
# 8. CI
mkdir -p .github/workflows
# 创建 ci.yml 跑 eslint + prettier --check + tsc --noEmit最终 package.json 关键字段:
json
{
"scripts": {
"prepare": "husky",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,yml}": ["prettier --write"]
}
}加分回答:
- 用
nx/turborepo模板能一键拥有这一切 - Antfu(Vue 团队)的
@antfu/eslint-config是社区流行的预设 lefthook是 Husky 的 Go 替代,更快且配置统一- CI 中可加
actions/cache缓存 ESLint cache 加速 - 配合
czg(commitizen 中文交互工具)让国内同事更友好
追问:
- 怎么做一个内部的 eslint-config-myorg 在多个项目共用?
- 这套配置在 monorepo 怎么调整?