第七部分 - 第 1 节 | 面向 Java 工程师的 TypeScript 学习指南
在 Java 世界中,你可能已经熟悉 Checkstyle、PMD、SpotBugs 等代码质量工具,以及 Maven enforcer 来强制执行规范。TypeScript/JavaScript 生态有一套对应的成熟工具链:
| 用途 | Java 工具 | TypeScript/JS 工具 |
|---|---|---|
| 代码规范检查 | Checkstyle / PMD | ESLint |
| 代码格式化 | google-java-format / Spotless | Prettier |
| 类型检查 | javac 本身 | tsc(TypeScript 编译器) |
| Git 提交钩子 | Maven enforcer / Git hooks | husky + lint-staged |
| 提交信息规范 | 无标准工具 | commitlint |
ESLint 是 JavaScript/TypeScript 生态最主流的静态代码分析工具。它可以:
- 发现代码中的问题(类似 PMD/SpotBugs)
- 强制代码风格规范(类似 Checkstyle)
- 自动修复部分问题
Java 对比:Checkstyle 侧重代码格式,PMD 侧重代码质量,而 ESLint 把两者合并到一个工具中。
# 初始化项目(如果还没有)
npm init -y
# 安装 ESLint 和 TypeScript 支持
npm install -D eslint @eslint/js typescript-eslint typescriptESLint v9+ 使用全新的 Flat Config 格式,配置文件为 eslint.config.js(不再是 .eslintrc)。
注意:如果你搜索到的教程还在用
.eslintrc.json,那是旧版配置格式。新项目请统一使用 Flat Config。
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
// 1. ESLint 内置推荐规则
eslint.configs.recommended,
// 2. TypeScript ESLint 推荐规则
...tseslint.configs.recommended,
// 3. 自定义配置
{
// 指定适用的文件
files: ['src/**/*.ts'],
languageOptions: {
parserOptions: {
// 启用类型感知的规则(需要 tsconfig)
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// ===== 关闭不需要的规则 =====
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_', // 允许 _xxx 形式的未使用参数
varsIgnorePattern: '^_',
}],
// ===== 类型安全规则 =====
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn',
// ===== 最佳实践 =====
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
// ===== 代码风格 =====
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
}],
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
},
},
// 4. 忽略特定目录
{
ignores: ['dist/', 'node_modules/', '*.js'],
},
);Java 工程师理解方式:
eslint.configs.recommended类似 Checkstyle 的google_checks.xml预设tseslint.configs.recommended类似为 Java 17+ 特性增加的额外规则rules部分类似在checkstyle.xml中自定义每条规则的 severity
TypeScript ESLint 提供了三个级别的规则集:
// eslint.config.js
import tseslint from 'typescript-eslint';
export default tseslint.config(
// 级别 1:基本推荐(不需要类型信息)
...tseslint.configs.recommended,
// 级别 2:严格模式(不需要类型信息,更严格)
// ...tseslint.configs.strict,
// 级别 3:需要类型信息的规则(最完整,但需要 tsconfig)
// ...tseslint.configs.recommendedTypeChecked,
// ...tseslint.configs.strictTypeChecked,
);常用 TypeScript ESLint 规则说明:
| 规则 | 说明 | Java 对比 |
|---|---|---|
no-explicit-any |
禁止使用 any 类型 |
类似禁止 raw type |
no-floating-promises |
Promise 必须被处理 | 类似 checked exception |
no-misused-promises |
防止 Promise 误用 | 无直接对比 |
strict-boolean-expressions |
条件表达式必须是 boolean | Java 本身就要求 |
no-unnecessary-condition |
检查永远为真/假的条件 | 类似 SpotBugs 的死代码检查 |
prefer-nullish-coalescing |
使用 ?? 替代 || |
无对比(JS 特有问题) |
# 检查所有 TypeScript 文件
npx eslint src/
# 自动修复可以修复的问题
npx eslint src/ --fix
# 只检查特定文件
npx eslint src/index.ts在 package.json 中添加脚本:
{
"scripts": {
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix"
}
}ESLint 支持编写自定义规则(类似 PMD 自定义规则)。大多数情况下你不需要自定义,但了解原理有帮助:
// eslint-plugin-custom/no-console-log.js
// 自定义规则:禁止 console.log(允许 console.warn/error)
export default {
meta: {
type: 'suggestion',
docs: {
description: '禁止使用 console.log',
},
fixable: 'code',
schema: [],
},
create(context) {
return {
// 访问 AST 中的 MemberExpression 节点
MemberExpression(node) {
if (
node.object.type === 'Identifier' &&
node.object.name === 'console' &&
node.property.type === 'Identifier' &&
node.property.name === 'log'
) {
context.report({
node,
message: '不允许使用 console.log,请使用 logger 工具',
});
}
},
};
},
};在配置中使用自定义规则:
// eslint.config.js
import noConsoleLog from './eslint-plugin-custom/no-console-log.js';
export default [
{
plugins: {
custom: {
rules: {
'no-console-log': noConsoleLog,
},
},
},
rules: {
'custom/no-console-log': 'error',
},
},
];Prettier 是一个有主见的(opinionated)代码格式化工具。它不关心代码逻辑,只关心代码长什么样。
Java 对比:类似 google-java-format——你几乎不需要配置,它帮你统一代码风格。
ESLint vs Prettier 的分工:
- ESLint:代码质量(发现 bug、最佳实践)+ 少量格式规则
- Prettier:纯粹的代码格式化(缩进、换行、引号等)
npm install -D prettier创建配置文件 .prettierrc:
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}配置项说明:
| 选项 | 值 | 说明 |
|---|---|---|
semi |
true |
行尾加分号(Java 工程师习惯) |
singleQuote |
true |
使用单引号(JS/TS 社区惯例) |
tabWidth |
2 |
缩进 2 空格(JS 惯例,Java 通常 4 空格) |
trailingComma |
"all" |
尾随逗号(便于 git diff) |
printWidth |
100 |
行宽 100 字符 |
arrowParens |
"always" |
箭头函数始终加括号 (x) => x |
创建忽略文件 .prettierignore:
dist/
node_modules/
*.md
运行 Prettier:
# 检查格式
npx prettier --check src/
# 自动格式化
npx prettier --write src/在 package.json 中添加脚本:
{
"scripts": {
"format": "prettier --write src/",
"format:check": "prettier --check src/"
}
}ESLint 有一些格式相关的规则(比如缩进、引号),会和 Prettier 冲突。解决方案是安装 eslint-config-prettier 来关闭 ESLint 中所有与 Prettier 冲突的规则:
npm install -D eslint-config-prettier// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
// 放在最后,关闭冲突的格式规则
eslintConfigPrettier,
{
files: ['src/**/*.ts'],
rules: {
// 你的自定义规则...
},
},
);工作流:
- ESLint 负责代码质量检查
- Prettier 负责代码格式化
eslint-config-prettier消除两者的规则冲突
TypeScript 的 strict: true 实际上是一组严格检查选项的集合:
{
"compilerOptions": {
"strict": true
// 等价于同时开启以下所有选项:
// "strictNullChecks": true, // null/undefined 必须显式处理
// "strictFunctionTypes": true, // 函数参数类型逆变检查
// "strictBindCallApply": true, // bind/call/apply 类型检查
// "strictPropertyInitialization": true, // 类属性必须初始化
// "noImplicitAny": true, // 禁止隐式 any
// "noImplicitThis": true, // 禁止隐式 this 类型
// "alwaysStrict": true, // 生成 "use strict"
// "useUnknownInCatchVariables": true // catch 变量为 unknown
}
}Java 工程师理解:
| 选项 | Java 对比 |
|---|---|
strictNullChecks |
类似 Java 的 @NonNull / @Nullable 注解,但编译器强制检查 |
strictPropertyInitialization |
类似 Java 中 final 字段必须在构造函数中初始化 |
noImplicitAny |
类似禁止使用 raw type(List 而非 List<String>) |
useUnknownInCatchVariables |
类似 Java 中 catch 不能直接当特定异常处理 |
除了 strict 包含的选项外,还有一些推荐开启的进阶选项:
{
"compilerOptions": {
// strict 之外的推荐选项
// 访问数组/对象索引时,类型包含 undefined
// 例如:arr[0] 的类型是 T | undefined,而非 T
"noUncheckedIndexedAccess": true,
// 未使用的局部变量报错
"noUnusedLocals": true,
// 未使用的函数参数报错
"noUnusedParameters": true,
// switch 语句必须有 default 或者覆盖所有 case
"noFallthroughCasesInSwitch": true,
// 确保 import 类型时使用 type 关键字
"verbatimModuleSyntax": true,
// 强制使用 .js 扩展名(ESM 规范)
"moduleResolution": "NodeNext",
"module": "NodeNext"
}
}重点:noUncheckedIndexedAccess
这是一个非常实用但常被忽略的选项:
const arr = [1, 2, 3];
// 没有 noUncheckedIndexedAccess:
const first = arr[0]; // 类型是 number —— 但如果数组为空呢?
// 开启 noUncheckedIndexedAccess:
const first = arr[0]; // 类型是 number | undefined —— 更安全!
// 使用时必须检查
if (first !== undefined) {
console.log(first.toFixed(2)); // 这里 first 是 number
}
// 或者用非空断言(确信不为空时)
const definitelyFirst = arr[0]!; // 类型是 numberJava 对比:Java 中 array[0] 如果越界会抛出 ArrayIndexOutOfBoundsException。TypeScript 开启此选项后,在编译时就提醒你处理可能的 undefined。
{
"compilerOptions": {
// ===== 类型检查(最严格)=====
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noPropertyAccessFromIndexSignature": true,
"exactOptionalPropertyTypes": true,
// ===== 编译目标 =====
"target": "ES2022",
"lib": ["ES2022"],
// ===== 模块系统 =====
"module": "NodeNext",
"moduleResolution": "NodeNext",
"verbatimModuleSyntax": true,
// ===== 输出 =====
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
// ===== 互操作性 =====
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
// ===== 其他 =====
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}Git Hooks 允许在 Git 操作的特定时机执行自定义脚本。最常用的场景:
- pre-commit:提交前检查代码质量和格式
- commit-msg:检查提交信息是否符合规范
Java 对比:类似 Maven enforcer 插件在 validate 阶段检查,或者 CI 中的 quality gate,但更早——在本地提交时就检查。
husky 是管理 Git Hooks 的工具:
# 安装
npm install -D husky
# 初始化(创建 .husky/ 目录)
npx husky init这会:
- 创建
.husky/目录 - 在
package.json中添加prepare脚本 - 创建一个示例
pre-commithook
编辑 .husky/pre-commit:
# .husky/pre-commit
npx lint-staged编辑 .husky/commit-msg:
# .husky/commit-msg
npx --no -- commitlint --edit $1lint-staged 只对 Git 暂存区(staged) 的文件执行检查,而非全部文件。这样大型项目也能快速完成检查。
Java 对比:类似只对你改动的文件执行 Checkstyle,而非整个项目。
npm install -D lint-staged在 package.json 中配置:
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}工作流:
git add src/index.ts
git commit -m "feat: add user login"
└── 触发 pre-commit hook
└── lint-staged 执行
├── eslint --fix src/index.ts
└── prettier --write src/index.ts
└── 如果检查通过,提交成功
└── 如果检查失败,提交被阻止
commitlint 强制提交信息遵循 Conventional Commits 格式:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
常见 type:
| type | 说明 | 示例 |
|---|---|---|
feat |
新功能 | feat(auth): add JWT login |
fix |
修复 bug | fix(api): handle null response |
docs |
文档变更 | docs: update README |
style |
格式调整(不影响逻辑) | style: fix indentation |
refactor |
重构 | refactor(user): extract validation |
test |
测试 | test: add login unit tests |
chore |
构建/工具变更 | chore: update eslint config |
安装和配置:
npm install -D @commitlint/cli @commitlint/config-conventional创建 commitlint.config.js:
// commitlint.config.js
export default {
extends: ['@commitlint/config-conventional'],
rules: {
// type 必须是以下之一
'type-enum': [
2, // 错误级别:0=关闭, 1=警告, 2=错误
'always',
[
'feat', 'fix', 'docs', 'style', 'refactor',
'test', 'chore', 'perf', 'ci', 'build', 'revert',
],
],
// 主题不超过 72 个字符
'subject-max-length': [2, 'always', 72],
},
};验证:
# 合法的提交信息
echo "feat(auth): add JWT authentication" | npx commitlint
# 不合法的提交信息(会报错)
echo "added some stuff" | npx commitlint
# => ✖ subject may not be empty [subject-empty]
# => ✖ type may not be empty [type-empty]下面是一个完整的 TypeScript 项目的代码质量工具配置。
{
"name": "my-ts-project",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"dev": "tsx watch src/index.ts",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"check": "tsc --noEmit && eslint src/ && prettier --check src/",
"prepare": "husky"
},
"devDependencies": {
"@commitlint/cli": "^19.0.0",
"@commitlint/config-conventional": "^19.0.0",
"@eslint/js": "^9.0.0",
"eslint": "^9.0.0",
"eslint-config-prettier": "^9.0.0",
"husky": "^9.0.0",
"lint-staged": "^15.0.0",
"prettier": "^3.0.0",
"typescript": "^5.5.0",
"typescript-eslint": "^8.0.0"
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml}": [
"prettier --write"
]
}
}import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintConfigPrettier,
{
files: ['src/**/*.ts'],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
}],
},
},
{
ignores: ['dist/', 'node_modules/'],
},
);{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"verbatimModuleSyntax": true,
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"sourceMap": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}export default {
extends: ['@commitlint/config-conventional'],
};# 完整的项目初始化流程
npm init -y
npm install -D typescript @eslint/js eslint typescript-eslint \
eslint-config-prettier prettier \
husky lint-staged \
@commitlint/cli @commitlint/config-conventional
# 初始化 TypeScript
npx tsc --init
# 初始化 husky
npx husky init
# 创建 pre-commit hook
echo "npx lint-staged" > .husky/pre-commit
# 创建 commit-msg hook
echo 'npx --no -- commitlint --edit $1' > .husky/commit-msg在团队中,推荐添加 VS Code 工作区设置,让编辑器自动应用格式化:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}{
"recommendations": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-vscode.vscode-typescript-next"
]
}| 工具 | 职责 | 运行时机 |
|---|---|---|
| TypeScript (tsc) | 类型检查 | 开发时 / CI |
| ESLint | 代码质量 + 最佳实践 | 开发时 / pre-commit / CI |
| Prettier | 代码格式化 | 保存时 / pre-commit / CI |
| husky | 管理 Git Hooks | git commit 时 |
| lint-staged | 只检查暂存文件 | pre-commit 时 |
| commitlint | 提交信息规范 | commit-msg 时 |
推荐的 CI 检查命令:
# 在 CI 中运行所有检查
tsc --noEmit # 类型检查(不生成文件)
eslint src/ # 代码质量检查
prettier --check src/ # 格式检查下一节:我们将学习如何为 TypeScript 项目编写测试,使用 Vitest 框架进行单元测试、集成测试和 Mock。