Skip to content

第 18 章 · 面试题集(代码质量)

Q1:ESLint 和 Prettier 有什么区别?为什么要一起用?

考察点:基础概念区分

标准答案

维度ESLintPrettier
目标找代码错误逻辑/风格问题统一代码格式
范围全方位(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-prettiereslint-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!: 重构用户模块(! 表示破坏性变更)

为什么要遵守

  1. 自动生成 changelog(standard-version、changesets 工具能直接产出)
  2. 自动决定版本号(feat → minor、fix → patch、! → major)
  3. 历史可读:搜 feat(auth) 直接看到所有 auth 相关功能新增
  4. CI 集成:可根据 type 跳过/触发不同流水线
  5. 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 Configeslint.config.js)是 ESLint 8.21+ 推出、9.0+ 成为默认的新一代配置格式。相比旧的 .eslintrc.* 有四大优势:

  1. 结构扁平:用数组按顺序合并,不再有 extends 字符串魔法
  2. 真正的 JS 模块:用 import 直接引入插件,而不是字符串名(如 'plugin:react/recommended'
  3. 可预测:配置合并逻辑更清晰,调试容易
  4. 现代化:天然支持 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。原因:

  1. tsc --noEmit 必须扫整个项目(不能只检查暂存文件,因为 TS 类型有跨文件依赖)
  2. 中型项目 tsc 一次要 5~30 秒,每次 commit 都等很烦
  3. 与 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 1
yaml
# 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:编辑器格式化设置混乱怎么办?

考察点:团队协作规范

标准答案

层层兜底:

  1. .editorconfig:编辑器原生支持,告诉 VSCode/IDEA/Sublime 缩进、字符集、换行符
  2. VSCode 项目级配置.vscode/settings.json):约定保存时格式化、默认 formatter
  3. VSCode 推荐扩展.vscode/extensions.json):让新人 clone 项目时弹窗提示装 ESLint/Prettier
  4. Prettier 配置.prettierrc
  5. 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-commit framework 替代 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 怎么调整?