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 | 运行时 + 编译时安全 |
参考与延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。