TypeScript 的价值不在「有类型」,而在「类型即文档、类型即约束、类型即重构安全网」。 一个配置得当的 TS 项目能让 IDE 自动补全成为你的「结对程序员」,让重命名变量不再恐惧,让 API 变更的破坏面一望可知。
一、tsconfig 严格模式配置
1.1 企业级推荐配置
{
"compilerOptions": {
// 目标与模块
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// 严格模式(全部开启)
"strict": true, // 启用所有严格类型检查
"noImplicitAny": true, // 禁止隐式 any
"strictNullChecks": true, // null/undefined 严格检查
"strictFunctionTypes": true, // 函数参数双向协变检查
"strictBindCallApply": true, // bind/call/apply 严格类型
"strictPropertyInitialization": true, // 类属性必须初始化
"noImplicitThis": true, // this 隐式 any 报错
"alwaysStrict": true, // 严格模式解析 + 输出 "use strict"
// 进阶严格(TS 4.x+)
"noUncheckedIndexedAccess": true, // 索引访问返回 T | undefined
"exactOptionalPropertyTypes": true, // 区分 undefined 与可选属性
"noImplicitReturns": true, // 所有分支必须有返回值
"noFallthroughCasesInSwitch": true, // switch case 禁止贯穿
"noUncheckedSideEffectImports": true, // 禁止无副作用的 import
// 模块与解析
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true, // 跳过 .d.ts 类型检查(加快编译)
"resolveJsonModule": true,
// 声明与输出
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
// 路径映射(Monorepo 关键)
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@my-org/shared": ["../packages/shared/src"],
"@my-org/ui": ["../packages/ui/src"]
}
},
"include": ["src/**/*", "tests/**/*"],
"exclude": ["node_modules", "dist"]
}
1.2 关键严格选项详解
noUncheckedIndexedAccess
// 开启前(危险)
const users: User[] = fetchUsers();
const first = users[0]; // Type: User(实际可能 undefined)
first.name; // 💥 运行时错误!
// 开启后(安全)
const first = users[0]; // Type: User | undefined
first.name; // ❌ TS 报错:first 可能为 undefined
first?.name; // ✅ 可选链
exactOptionalPropertyTypes
interface Config {
timeout?: number;
}
// 开启前
const c1: Config = { timeout: undefined }; // ✅ 允许(但实际不需要)
// 开启后
const c1: Config = { timeout: undefined }; // ❌ 报错:timeout 是可选,不是可 undefined
// 正确做法
const c2: Config = {}; // ✅ 不包含 timeout
const c3: Config = { timeout: 5000 }; // ✅ 有具体值
二、高级类型体操
2.1 条件类型与 infer
// 从 Promise<T> 中提取 T
type Awaited<T> = T extends Promise<infer U> ? U : T;
type Result = Awaited<Promise<string>>; // string
type Result2 = Awaited<number>; // number
// 提取函数返回值
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 提取数组元素
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Item = ElementOf<string[]>; // string
2.2 映射类型改造
// 全部变为只读
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// 全部变为可选
type Partial<T> = { [K in keyof T]?: T[K] };
// 全部变为 required(-? 移除可选修饰符)
type Required<T> = { [K in keyof T]-?: T[K] };
// 只选取部分 key
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// 排除部分 key
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 自定义:所有属性变为 string 或 null
type NullableString<T> = {
[K in keyof T]: string | null;
};
// 自定义:带前缀的属性名
type PrefixKeys<T, P extends string> = {
[K in keyof T as `${P}${string & K}`]: T[K];
};
type User = { name: string; age: number };
type ApiUser = PrefixKeys<User, 'api_'>;
// { api_name: string; api_age: number }
2.3 模板字面量类型
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint<M extends HttpMethod, P extends string> = `/${Lowercase<M>}/${P}`;
type UserEndpoint = Endpoint<'GET', 'users'>; // "/get/users"
// EventName 映射:onClick → handleClick
type EventHandlers<T extends string> = {
[K in T as `on${Capitalize<K>}`]: () => void;
};
type Events = EventHandlers<'click' | 'hover' | 'focus'>;
// { onClick: () => void; onHover: () => void; onFocus: () => void }
2.4 递归类型
// 深度 Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
// JSON 类型
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
三、类型守卫与 Narrowing
3.1 自定义类型守卫
interface Cat {
type: 'cat';
meow(): void;
}
interface Dog {
type: 'dog';
bark(): void;
}
type Animal = Cat | Dog;
// 自定义类型守卫函数
function isCat(animal: Animal): animal is Cat {
return animal.type === 'cat';
}
function makeSound(animal: Animal) {
if (isCat(animal)) {
animal.meow(); // ✅ TS 知道是 Cat
} else {
animal.bark(); // ✅ TS 知道是 Dog
}
}
3.2 类型谓词与类守卫
// 非空断言守卫
function isDefined<T>(value: T | undefined | null): value is T {
return value !== undefined && value !== null;
}
const items = [1, null, 3, undefined, 5];
const valid = items.filter(isDefined); // number[]
// 类实例守卫
class ApiError extends Error {
constructor(public statusCode: number) {
super();
}
}
function handleError(err: unknown) {
if (err instanceof ApiError) {
console.log(err.statusCode); // ✅ number
} else if (err instanceof Error) {
console.log(err.message);
}
}
四、泛型约束与协变逆变
4.1 泛型约束
// 约束 T 必须有 length 属性
function logLength<T extends { length: number }>(arg: T): T {
console.log(arg.length);
return arg;
}
logLength('hello'); // ✅ string 有 length
logLength([1, 2, 3]); // ✅ array 有 length
logLength(123); // ❌ number 没有 length
// 多泛型约束
type HasId = { id: string };
function updateEntity<T extends HasId>(
entity: T,
updates: Partial<Omit<T, 'id'>>
): T {
return { ...entity, ...updates };
}
const user = { id: 'u1', name: 'Alice', age: 30 };
const updated = updateEntity(user, { name: 'Bob' });
// 不能修改 id!由类型系统保证
4.2 协变与逆变
// 协变(Covariance):子类型可赋值给父类型
interface Animal { name: string }
interface Dog extends Animal { breed: string }
let animals: Animal[] = [];
let dogs: Dog[] = [{ name: 'Buddy', breed: 'Golden' }];
animals = dogs; // ✅ 协变:Dog[] 是 Animal[] 的子类型
// 逆变(Contravariance):函数参数位置
let animalHandler: (a: Animal) => void;
let dogHandler: (d: Dog) => void;
dogHandler = animalHandler; // ✅ 逆变:处理 Animal 的函数可以处理 Dog
// animalHandler = dogHandler; // ❌ 不能反向
五、品牌类型(Branded Types)
用「名义类型」在结构类型系统中区分相同形状的值。
// 用户 ID 和订单 ID 都是 string,但不能混用
type UserId = string & { __brand: 'UserId' };
type OrderId = string & { __brand: 'OrderId' };
function createUserId(id: string): UserId {
return id as UserId;
}
function createOrderId(id: string): OrderId {
return id as OrderId;
}
function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }
const uid = createUserId('u-123');
const oid = createOrderId('o-456');
getUser(uid); // ✅
getUser(oid); // ❌ 类型错误!
六、Monorepo 类型共享
6.1 包内类型导出
// packages/shared/package.json
{
"name": "@my-org/shared",
"version": "1.0.0",
"exports": {
"./types": {
"types": "./dist/types/index.d.ts",
"default": "./dist/types/index.js"
},
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
}
}
// packages/shared/src/types/api.ts
export interface ApiResponse<T> {
data: T;
meta: {
page: number;
pageSize: number;
total: number;
};
}
export interface ApiError {
code: string;
message: string;
details?: Record<string, string[]>;
}
export type Result<T> =
| { success: true; data: T }
| { success: false; error: ApiError };
6.2 应用层消费
// apps/web/src/services/user.ts
import type { ApiResponse, Result } from '@my-org/shared/types';
import type { User } from '@my-org/shared';
export async function fetchUsers(): Promise<Result<User[]>> {
const res = await fetch('/api/users');
if (!res.ok) {
return { success: false, error: await res.json() };
}
return { success: true, data: (await res.json()).data };
}
七、API 类型自动同步
7.1 OpenAPI → TypeScript
# 安装
npm install -D openapi-typescript
# 从 OpenAPI JSON 生成类型
npx openapi-typescript https://api.example.com/openapi.json \
-o src/types/api.ts
// 生成的 api.ts(自动生成,不要手动修改)
export interface paths {
'/users': {
get: {
responses: {
200: {
content: {
'application/json': {
data: components['schemas']['User'][];
meta: components['schemas']['PaginationMeta'];
};
};
};
};
};
};
}
export interface components {
schemas: {
User: {
id: string;
name: string;
email: string;
createdAt: string;
};
};
}
7.2 类型安全 fetch 封装
import type { paths, components } from './types/api';
// 路径参数提取
type Path = keyof paths;
type Method<P extends Path> = keyof paths[P] & string;
type Response<P extends Path, M extends Method<P>> =
paths[P][M] extends { responses: infer R }
? R extends { 200: { content: { 'application/json': infer D } }
? D : never
: never;
async function apiFetch<P extends Path, M extends Method<P>>(
path: P,
method: M
): Promise<Response<P, M>> {
const res = await fetch(path, { method });
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
// 使用:类型自动推导
const users = await apiFetch('/users', 'get');
// users 类型 = { data: User[]; meta: PaginationMeta }
八、Vue 与 React 类型实践
8.1 Vue 3 + TypeScript
<script setup lang="ts">
// 泛型组件 Props
interface Props {
items: T[];
labelKey: keyof T;
}
const props = defineProps<Props>();
const emit = defineEmits<{
select: [item: T];
remove: [index: number];
}>();
// 类型安全的 ref
const selectedIndex = ref<number | null>(null);
// 类型安全的 computed
const selectedItem = computed(() => {
if (selectedIndex.value === null) return null;
return props.items[selectedIndex.value]; // noUncheckedIndexedAccess 确保安全
});
</script>
8.2 React + TypeScript
// 泛型组件
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={keyExtractor(item)}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// 使用:类型自动推导
<List
items={users}
renderItem={(user) => <span>{user.name}</span>}
keyExtractor={(user) => user.id}
/>
// forwardRef + 类型
import { forwardRef } from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary';
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ variant = 'primary', ...props }, ref) => {
return (
<button
ref={ref}
className={`btn btn--${variant}`}
{...props}
/>
);
}
);
Button.displayName = 'Button';
九、类型安全 Checklist
| 检查项 | 配置/做法 | 收益 |
|---|---|---|
| 开启 strict 全家桶 | "strict": true | 消除隐式 any |
| 索引访问安全 | noUncheckedIndexedAccess | 防数组越界 |
| 可选属性精确 | exactOptionalPropertyTypes | 区分可选与 undefined |
| API 类型同步 | openapi-typescript | 前后端类型一致 |
| 品牌类型 | type UserId = string & { __brand: 'UserId' } | 防 ID 混用 |
| Monorepo 类型共享 | 包内 exports.types + paths 映射 | 跨包类型安全 |
| 类型守卫函数 | function isX(v): v is X | 运行时 + 编译时安全 |
十、TypeScript 5.x 新特性跟进
10.1 Decorators(TC39 Stage 3)
TS 5.0 实现了新的装饰器标准,告别实验性 experimentalDecorators:
// 类装饰器
function logged<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
return function (this: This, ...args: Args): Return {
console.log(`Calling ${String(context.name)}`);
return target.call(this, ...args);
};
}
class UserService {
@logged
getUser(id: string) {
return { id, name: 'Alice' };
}
}
10.2 const 类型参数
// TS 5.0: 让泛型推断更严格
function createArray<const T extends readonly string[]>(items: T): T {
return items;
}
const arr = createArray(['a', 'b']); // T = readonly ["a", "b"](字面量推断)
// 对比(不用 const 参数)
function createArrayLoose<T extends readonly string[]>(items: T): T {
return items;
}
const arr2 = createArrayLoose(['a', 'b']); // T = string[](推断变宽泛)
10.3 satisfies 运算符
// 保留字面量类型推断,同时做结构校验
const config = {
host: 'localhost',
port: 3000,
debug: true
} satisfies { host: string; port: number; debug?: boolean };
// config.host 的类型是 "localhost"(精确字面量),不是 string
// 如果少了 port,或类型不匹配,就会报错
十一、Zod 运行时类型安全
类型体操解决编译时安全,但外部数据(API 响应、表单输入)需要运行时校验:
import { z } from 'zod';
// 1. 定义 Schema
const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['user', 'admin', 'editor']),
tags: z.array(z.string()).max(10),
});
// 2. 推断 TypeScript 类型
type User = z.infer<typeof UserSchema>;
// 3. 运行时校验
function parseUser(data: unknown): User {
// 失败时抛出 ZodError,包含详细路径信息
return UserSchema.parse(data);
}
// 4. 安全解析(不抛异常)
function safeParseUser(data: unknown): { success: true; data: User } | { success: false; error: z.ZodError } {
return UserSchema.safeParse(data);
}
// 5. 与 API 层结合(OpenAPI → Zod → TS 类型链)
const apiUserSchema = UserSchema.extend({
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
});
async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
const json = await res.json();
return apiUserSchema.parse(json); // 运行时 + 编译时双重安全
}
Zod vs class-validator vs io-ts
| 特性 | Zod | class-validator | io-ts |
|---|---|---|---|
| 学习曲线 | 低 | 中(装饰器语法) | 高(函数式) |
| Tree-shaking | ✅ 好 | ❌ 依赖 class-transformer | ✅ 好 |
| 错误信息 | 极其详细 | 一般 | 需要自定义 |
| 生态整合 | tRPC, React Hook Form | NestJS 原生 | fp-ts 生态 |
| 推荐 | ✅ 新项目首选 | 遗留 NestJS 项目 | 函数式团队 |
十二、类型级调试技巧
// Hover 调试法:在类型体操中加入"断点"
type Debug<T> = { [K in keyof T]: T[K] } & { __debug: T };
type MyComplexType<T> = T extends string
? T extends `/${infer R}` ? R : never
: never;
// 查看中间结果
type Step1 = MyComplexType<"/users/123">; // Hover 看推导过程
// 用 TSError 打印类型信息(利用 never 赋值报错)
type Print<T> = T extends string ? T : never;
const _test: Print<123> = "error"; // ❌ Type 'string' is not assignable to type 'never'
// 报错信息中可看到 123 经过转换后的类型
// VS Code 插件:TypeScript Importer / Error Lens
// 调试配置:"typescript.inlayHints.functionLikeReturnTypes.enabled": true
参考与延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。