TypeScript编程规范指南
# TypeScript编程代码规范指南
基于 TypeScript ESLint (opens new window) 推荐配置。
# 目录
- 01.规范概述
- 02.命名规范
- 03.类型与接口
- 04.函数规范
- 05.类设计规范
- 06.导入与模块
- 07.空安全与可选链
- 08.泛型使用规范
- 09.枚举与常量
- 10.异步与 Promise
- 11.工具链与自动化
- 12.代码审查清单
- 13.常见陷阱速查
# 01.规范概述
# 1.1 为何需要 TypeScript 规范
疑惑:TypeScript 编译器已经能检查类型了,为什么还需要编码规范?
答疑:TypeScript 的类型系统非常灵活——同一目标有 5 种类型实现方式。interface 还是 type?unknown 还是 any?enum 还是 as const?规范的本质是在灵活性中统一选择,让团队代码像一个模式的重复应用。
# 1.2 核心目标
| 目标 | 说明 |
|---|---|
| 类型安全 | 利用 TS 类型系统在编译期消除一类运行时 bug |
| 可维护性 | 类型即文档,新成员看类型签名就能理解 API |
| 一致性 | 全项目 interface/type、泛型风格统一 |
| 可审查性 | Code Review 聚焦逻辑和类型设计,而非风格争论 |
# 1.3 要求等级
- 必须(Mandatory):必须采用(CI 中强制检查)
- 推荐(Preferable):理应采用,特殊情况可不采用
- 可选(Optional):自行决定
# 02.命名规范
| 类型 | 规范 | 示例 |
|---|---|---|
| 变量/函数 | camelCase | userName, getUserById() |
| 类/接口/类型 | PascalCase | UserService, ApiResponse |
| 常量 | UPPER_CASE 或 camelCase | MAX_RETRY 或 maxRetry |
| 枚举成员 | PascalCase | OrderStatus.Pending |
| 私有成员 | 不强制前缀 | private userId: number |
| 文件 | kebab-case 或 camelCase | user-service.ts, userService.ts |
// ✅ 类型定义大写开头
interface UserProfile {
id: number;
name: string;
}
// ✅ 优先 interface,需要联合类型时用 type
interface ApiResponse<T> { data: T; code: number; }
type Status = 'active' | 'inactive' | 'pending';
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 03.类型与接口
# 3.1 interface 与 type 的选择
// ✅ 定义对象结构:优先 interface
interface User {
id: number;
name: string;
email?: string;
}
// ✅ 联合类型 / 交叉类型 / 映射类型:用 type
type Result<T> = { success: true; data: T } | { success: false; error: string };
type Admin = User & { permissions: string[] };
type ReadonlyUser = Readonly<User>;
// ✅ 函数签名:两者皆可,团队统一即可
type Fn = (x: number) => string;
interface Fn { (x: number): string; }
// ✅ 禁止空的 interface / type
// ❌ interface Empty {}
// ❌ type Empty = {};
// ✅ 类型断言用 as
const el = document.getElementById('app') as HTMLDivElement;
// ❌ 禁止 <Type> 断言(与 JSX 冲突)
// ✅ 声明顺序:static > instance, field > constructor > method, public > protected > private
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 3.2 辨识联合类型(Discriminated Union)【推荐】
// ✅ 用字面量字段作为"标签"来区分联合类型
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'rectangle'; width: number; height: number }
| { kind: 'triangle'; base: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2; // 类型自动收窄
case 'rectangle':
return shape.width * shape.height;
case 'triangle':
return (shape.base * shape.height) / 2;
}
// ✅ TS 会检查 switch 是否覆盖所有分支(exhaustiveness check)
}
// ❌ 反模式:用可选字段区分类型
interface Event {
type: string;
userId?: string; // 只存在于 UserEvent
orderId?: string; // 只存在于 OrderEvent
}
// → 调用方不知道哪些字段何时存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 3.3 类型收窄(Type Narrowing)【推荐】
// ✅ typeof 收窄
function padLeft(value: string | number, padding: string | number) {
if (typeof padding === 'number') {
return ' '.repeat(padding) + value; // padding 收窄为 number
}
return padding + value; // padding 收窄为 string
}
// ✅ instanceof 收窄
function handleError(err: Error | string) {
if (err instanceof Error) {
console.error(err.message); // err 收窄为 Error
} else {
console.error(err); // err 收窄为 string
}
}
// ✅ 自定义类型守卫(is)
function isUser(obj: unknown): obj is User {
return typeof obj === 'object' && obj !== null && 'id' in obj && 'name' in obj;
}
// ✅ in 操作符收窄
if ('status' in response) {
// response 收窄为包含 status 的类型
}
// ❌ 避免用 any 强制转换(放弃类型检查)
const data = response as any; // ❌
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 3.4 satisfies 操作符(TS 4.9+)【推荐】
// ✅ satisfies:既检查类型,又保留字面量推导
const palette = {
red: [255, 0, 0],
green: '#00ff00',
blue: [0, 0, 255],
} satisfies Record<string, string | number[]>;
// ✅ 每个属性的精确类型被保留
palette.red.push(128); // ✅ 推导为 number[]
palette.green.toUpperCase(); // ✅ 推导为 string
// ✅ 用在路由配置、API 映射等场景
const routes = {
home: '/',
user: '/user/:id',
settings: '/settings',
} satisfies Record<string, string>;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 04.函数规范
# 4.1 参数与返回值类型
function getUser(id: number): Promise<User | null> { return db.users.findUnique({ where: { id } }); }
// ✅ 可选参数用 ?,有默认值时可省略类型标注 function connect(host: string, port = 8080, ssl?: boolean): Connection { // ... }
// ✅ 函数重载:按顺序声明 function parse(input: string): number; function parse(input: string[]): number[]; function parse(input: string | string[]): number | number[] { // 实现 }
// ✅ 优先用联合类型而非同名但参数不同的函数 function log(message: string, level?: 'info' | 'warn' | 'error'): void { ... }
### 4.2 函数重载
```typescript
// ✅ 重载签名在上,实现签名在下(实现签名不对外)
function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]): number | number[] {
if (typeof input === 'string') return parseInt(input, 10);
return input.map(s => parseInt(s, 10));
}
// ✅ 字符串模式重载(常用场景)
function createElement(tag: 'a'): HTMLAnchorElement;
function createElement(tag: 'div'): HTMLDivElement;
function createElement(tag: 'img'): HTMLImageElement;
function createElement(tag: string): HTMLElement {
return document.createElement(tag);
}
// ❌ 重载数量不宜过多(>3 个考虑是否设计有问题)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 4.3 this 参数类型 【推荐】
// ✅ 声明 this 类型(伪参数,编译后擦除)
class Button {
private disabled = false;
click(this: Button) { // 强制 this 为 Button 实例
if (this.disabled) return;
// ...
}
}
// ❌ 如果回调中会丢失 this 绑定,TS 会报错
// button.click.call(null); // ❌ 编译错误
// ✅ 回调场景
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
// this: void 表示回调内部不应该使用 this
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 05.类设计规范
// ✅ 使用 ES6 class,避免原型链直接操作
class OrderService {
// 静态成员在最前
private static instance: OrderService;
// 实例字段
private readonly repository: OrderRepository;
// 构造函数
public constructor(repository: OrderRepository) {
this.repository = repository;
}
// 公共方法
public async create(data: CreateOrderDto): Promise<Order> { ... }
// 私有方法
private async validate(data: CreateOrderDto): Promise<boolean> { ... }
}
// ✅ 禁止 this 赋值给临时变量(用箭头函数)
class Timer {
start() {
setTimeout(() => {
this.onTick(); // ✅ 箭头函数自动绑定
}, 1000);
}
}
// ❌ 禁止 constructor 参数加修饰符简化声明(保持清晰)
// 建议显式声明字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 06.导入与模块
// ✅ 用 import 而非 require
import fs from 'fs';
import { join } from 'path';
// ❌ const fs = require('fs');
// ✅ 禁止三斜杠引用
// ❌ /// <reference path="./module" />
// ✅ 导入类型用 import type
import type { User } from './models';
import { createUser, type CreateUserDto } from './service';
// ✅ 导出
export { UserService, type UserConfig };
export default UserService;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 07.空安全与可选链
// ✅ 可选链(?.)
const city = user?.address?.city;
// ✅ 空值合并(??)
const name = user?.name ?? 'Unknown';
// ✅ 非空断言(仅在你确定非空时)
const el = document.getElementById('app')!;
// ❌ 可选链后不跟非空断言
// user?.name! // 多余
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 08.泛型使用规范
# 8.1 基础泛型与约束
// ✅ 有意义的泛型名(T, U, K, V 是惯例)
function map<T, U>(items: T[], fn: (item: T) => U): U[] {
return items.map(fn);
}
// ✅ 泛型约束(extends)
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// ✅ 泛型默认值
interface Config<T = string> {
value: T;
validate(v: T): boolean;
}
// ✅ 泛型工厂函数
function create<T>(Ctor: new (...args: unknown[]) => T, ...args: unknown[]): T {
return new Ctor(...args);
}
// ❌ 避免不必要的泛型——能被推断就不加
const ids = [1, 2, 3]; // number[] 自动推断,不需要 <number>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 8.2 条件类型与 infer 【推荐】
// ✅ 条件类型:T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
// ✅ infer:从类型中提取部分
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
type ArrayElement<T> = T extends (infer E)[] ? E : never;
// ✅ 分布式条件类型(联合类型自动分发)
type ToArray<T> = T extends unknown ? T[] : never;
type X = ToArray<string | number>; // string[] | number[]
// ✅ 实用条件类型示例
type ApiResponse<T> =
T extends { error: string } ? ErrorResult
: T extends { data: unknown } ? SuccessResult<T['data']>
: never;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 8.3 模板字面量类型(TS 4.4+)【推荐】
// ✅ 字符串模式匹配
type EventName = `on${Capitalize<string>}`;
// 'onClick' | 'onChange' | 'onSubmit' | ...
type CSSProperty = 'margin' | 'padding';
type CSSDirection = 'Top' | 'Right' | 'Bottom' | 'Left';
type CSSKey = `${CSSProperty}${CSSDirection}`;
// 'marginTop' | 'marginRight' | ...
// ✅ 路由参数类型安全
type Route = `/user/${string}` | `/post/${number}`;
function navigate(path: Route) { }
navigate('/user/yc'); // ✅
// navigate('/admin/yc'); // ❌ 编译错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 09.枚举与常量
# 10.异步与 Promise
// ✅ Promise 明确返回类型
async function fetchUser(id: number): Promise<User> {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
// ✅ 条件中 Promise 必须 await
async function check(): Promise<void> {
// ❌ if (fetchUser(1)) { ... } // Promise 恒为 truthy
const user = await fetchUser(1);
if (user) { ... }
}
// ✅ 并发请求用 Promise.all
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id),
]);
// ✅ 用 try-catch 处理 async 错误
try {
const data = await riskyOperation();
} catch (error) {
logger.error('Operation failed', error);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 09.枚举与常量
// ✅ 字符串枚举优于数字枚举
enum OrderStatus {
Pending = 'pending',
Paid = 'paid',
Cancelled = 'cancelled',
}
// ✅ 常量枚举(内联优化)
const enum Direction { Up, Down, Left, Right }
// ✅ 需要常量对象的场景用 const + as const
const STATUS_MAP = {
pending: '待处理',
paid: '已支付',
} as const;
type Status = keyof typeof STATUS_MAP;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 11.工具链与自动化
# 11.1 tsconfig 严格模式
{
"compilerOptions": {
"strict": true, // 开启所有严格检查
"noImplicitReturns": true, // 函数所有路径必须返回值
"noUncheckedIndexedAccess": true, // 索引访问加 undefined
"noFallthroughCasesInSwitch": true, // switch 禁止穿透
"forceConsistentCasingInFileNames": true,// 文件名大小写一致
"noUnusedLocals": true, // 禁止未用的局部变量
"noUnusedParameters": true, // 禁止未用的参数
"exactOptionalPropertyTypes": true // 可选属性不能显式赋 undefined
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 11.2 ESLint 与 Prettier
# 安装
npm install -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
1
2
2
// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/strict',
],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/array-type': ['error', { default: 'generic' }],
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 11.3 CI 集成
# GitHub Actions
- name: Type Check
run: npx tsc --noEmit # 仅检查类型,不产出文件
- name: Lint
run: npx eslint . --ext .ts,.tsx
- name: Test
run: npx vitest run
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 12.代码审查清单
每次 Code Review 时,按以下清单逐项检查:
## 类型设计
- [ ] 无 any(除非有充分理由 + JSDoc 注释)
- [ ] 公开函数有明确的参数和返回值类型
- [ ] interface / type 无不明确成员
- [ ] 辨识联合的 switch 覆盖所有分支
- [ ] 类型收窄用 is 守卫而非 as 断言
## 命名与结构
- [ ] 变量/函数 camelCase,类/接口/类型 PascalCase
- [ ] 无拼音、无单字母(循环索引除外)
- [ ] 类型导入用 import type
## 安全
- [ ] 可选属性用 ?. 访问,空值用 ??
- [ ] 无不必要的 ! 非空断言
- [ ] async 函数有 try-catch 错误处理
- [ ] 条件中 Promise 必须 await
## 设计原则
- [ ] 优先 interface,联合类型 / 映射类型用 type
- [ ] 无空的 interface / type
- [ ] 泛型有意义的名称和约束
- [ ] 函数重载不超过 3 个签名
## 工程
- [ ] strict: true 无编译错误
- [ ] 无 eslint 告警
- [ ] import 顺序正确(外部→内部→类型)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 13.常见陷阱速查
# 13.1 类型陷阱
| # | 陷阱 | 正解 |
|---|---|---|
| 1 | null 和 undefined 不加检查→运行时 crash | 开启 strictNullChecks,或 value ?? fallback |
| 2 | as 断言绕过检查→类型不匹配编译通过 | 用类型守卫 is 收窄代替 as |
| 3 | any 污染→传了 any 的地方类型检查全败 | 用 unknown + 收窄,逐步消除 any |
| 4 | enum 编译后产生 JS 对象(有运行时开销) | 常量枚举 const enum 或字面量联合类型 'a' \| 'b' |
| 5 | 可选链后接非空断言 user?.name! | 去掉 !,user?.name 足矣 |
# 13.2 泛型陷阱
| # | 陷阱 | 正解 |
|---|---|---|
| 1 | 泛型不约束→类型信息丢失 | 加 extends 约束 |
| 2 | 函数泛型过度推断→返回值类型错 | 显式传入泛型参数 fn<User>(data) |
| 3 | 条件类型嵌套过深→类型报错不可读 | 拆成多个具名类型别名 |
| 4 | 用 Function 类型→丢失参数和返回值信息 | (...args: unknown[]) => unknown |
# 13.3 工程陷阱
| # | 陷阱 | 正解 |
|---|---|---|
| 1 | skipLibCheck: true 掩盖第三方类型错误 | 先修类型错误,万不得已再打开 |
| 2 | @ts-ignore / @ts-expect-error 泛滥 | 只在真正需要的行用 @ts-expect-error + 注释原因 |
| 3 | 不区分 import type 和普通 import | 类型导入用 import type(编译后擦除,无循环依赖风险) |
| 4 | tsconfig 不开启 strict→类型系统形同虚设 | "strict": true 是底线 |
| 5 | CI 不做 tsc --noEmit→合并后有类型错误 | CI 必须做类型检查 + lint |
上次更新: 2026/06/17, 11:39:29