架构视角:类型即文档
TypeScript不仅是一门编程语言,更是一种架构设计工具。优秀的类型系统能够在编译期捕获错误、提供智能提示、增强代码可读性,甚至指导软件架构设计。将类型视为不可执行的文档,是TypeScript进阶的关键思维转变。
TypeScript架构价值
- 编译期安全:在代码运行前发现类型错误,减少运行时异常
- 自文档化:类型定义本身就是最好的API文档
- 重构友好:IDE支持全局重命名、查找引用、安全重构
- 领域建模:使用类型系统表达业务领域模型
- 团队协作:类型契约明确组件接口,降低沟通成本
高级类型系统
条件类型与类型推断
// 条件类型基础
// 基础条件类型
type IsString = T extends string ? true : false;
type A = IsString; // true
type B = IsString; // false
// 分布式条件类型
type ToArray = T extends any ? T[] : never;
type C = ToArray; // string[] | number[]
// 非分布式条件类型(使用方括号包裹)
type ToArrayNonDist = [T] extends [any] ? T[] : never;
type D = ToArrayNonDist; // (string | number)[]
// infer关键字 - 类型推断
// 提取函数返回类型
type ReturnType = T extends (...args: any[]) => infer R ? R : never;
// 提取Promise解析类型
type Awaited = T extends Promise ? U : T;
// 提取数组元素类型
type ElementType = T extends (infer E)[] ? E : never;
// 提取对象属性类型
type PropType = T extends { [P in K]: infer V } ? V : never;
// 实用类型工具
// 深度Partial
type DeepPartial = {
[P in keyof T]?: T[P] extends object ? DeepPartial : T[P];
};
// 深度Readonly
type DeepReadonly = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
};
// 提取可选属性
type OptionalKeys = {
[K in keyof T]-?: {} extends Pick ? K : never;
}[keyof T];
// 提取必填属性
type RequiredKeys = {
[K in keyof T]-?: T extends Record ? K : never;
}[keyof T];
// 扁平化对象类型
type Flatten = T extends object ? T[keyof T] : T;
模板字面量类型
// 模板字面量类型
// 基础用法
type EventName = `on${Capitalize}`;
type ClickEvent = EventName<'click'>; // 'onClick'
// 组合多个类型
type HTTPMethod = 'get' | 'post' | 'put' | 'delete';
type Endpoint = '/users' | '/posts' | '/comments';
type APIPath = `${Uppercase} ${Endpoint}`;
// 'GET /users' | 'GET /posts' | ... | 'DELETE /comments'
// 对象属性路径类型
// 实现类似lodash.get的类型安全版本
type Path = K extends string
? T[K] extends Record
? `${K}` | `${K}.${Path}`
: `${K}`
: never;
interface User {
name: string;
address: {
street: string;
city: string;
country: {
code: string;
name: string;
};
};
}
type UserPath = Path;
// 'name' | 'address' | 'address.street' | 'address.city' |
// 'address.country' | 'address.country.code' | 'address.country.name'
// CSS变量类型
type CSSVariable = `--${T}`;
type ThemeColor = 'primary' | 'secondary' | 'success' | 'error';
type CSSCustomProperty = CSSVariable;
// '--primary' | '--secondary' | '--success' | '--error'
映射类型高级用法
// 映射类型进阶
// 重映射键名
type Getters = {
[K in keyof T as `get${Capitalize}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters;
// { getName: () => string; getAge: () => number; }
// 过滤属性
type FilterByValueType = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
type StringProps = FilterByValueType;
// { name: string; }
// 移除特定修饰符
type Mutable = {
-readonly [K in keyof T]: T[K];
};
type Optional = {
[K in keyof T]?: T[K];
};
type Required = {
[K in keyof T]-?: T[K];
};
// 递归映射类型
type DeepMutable = {
-readonly [K in keyof T]: T[K] extends object
? DeepMutable
: T[K];
};
// 键名转换
type CamelCase =
S extends `${infer P}_${infer Q}`
? `${P}${Capitalize>}`
: S;
type SnakeToCamel = {
[K in keyof T as CamelCase]: T[K] extends object
? SnakeToCamel
: T[K];
};
interface SnakeCaseData {
user_name: string;
created_at: string;
user_address: {
street_name: string;
zip_code: string;
};
}
type CamelCaseData = SnakeToCamel;
// {
// userName: string;
// createdAt: string;
// userAddress: { streetName: string; zipCode: string; };
// }
类型体操实战
实现高级类型工具
// 类型体操挑战
// 1. 实现Tuple转Union
type TupleToUnion = T[number];
type Result1 = TupleToUnion<['a', 'b', 'c']>; // 'a' | 'b' | 'c'
// 2. 实现Union转Intersection
type UnionToIntersection =
(U extends any ? (k: U) => void : never) extends (k: infer I) => void
? I
: never;
type Result2 = UnionToIntersection<{ a: string } | { b: number }>;
// { a: string } & { b: number }
// 3. 实现PickByType
type PickByType = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface Example {
name: string;
age: number;
isActive: boolean;
createdAt: Date;
}
type StringProps = PickByType; // { name: string; }
// 4. 实现DeepOmit
type DeepOmit = T extends object
? {
[P in keyof T as P extends K ? never : P]: T[P] extends object
? DeepOmit
: T[P];
}
: T;
// 5. 实现Curry类型
type Curry any> = T extends (
arg: infer A,
...rest: infer R
) => infer Ret
? R extends []
? (arg: A) => Ret
: (arg: A) => Curry<(...args: R) => Ret>
: T;
function add(a: number, b: number, c: number): number {
return a + b + c;
}
type CurriedAdd = Curry;
// (arg: number) => (arg: number) => (arg: number) => number
// 6. 实现IsNever
type IsNever = [T] extends [never] ? true : false;
// 7. 实现IsAny
type IsAny = 0 extends 1 & T ? true : false;
// 8. 实现Exact类型(精确匹配,不允许额外属性)
type Exact = T extends Shape
? Exclude extends never
? T
: never
: never;
// 9. 实现FlattenArray
type FlattenArray = T extends [infer F, ...infer R]
? F extends any[]
? [...FlattenArray, ...FlattenArray]
: [F, ...FlattenArray]
: [];
type Flattened = FlattenArray<[1, [2, 3], [[4]]]>;
// [1, 2, 3, 4]
// 10. 实现StringLength
type StringLength<
S extends string,
Acc extends any[] = []
> = S extends `${string}${infer Rest}`
? StringLength
: Acc['length'];
type Len = StringLength<'hello'>; // 5
类型安全的数据库查询
// 类型安全的数据库查询构建器
interface TableSchema {
users: {
id: number;
name: string;
email: string;
createdAt: Date;
};
posts: {
id: number;
title: string;
content: string;
authorId: number;
published: boolean;
};
}
type TableName = keyof TableSchema;
type QueryBuilder = {}
> = {
table: T;
select: Select[];
where?: Where;
limit?: number;
orderBy?: {
column: keyof TableSchema[T];
direction: 'asc' | 'desc';
};
};
function createQuery(table: T) {
return {
select: (...columns: K[]) => ({
where: (conditions: Partial) => ({
build: (): QueryBuilder> => ({
table,
select: columns,
where: conditions
})
})
})
};
}
// 使用
const query = createQuery('users')
.select('id', 'name', 'email')
.where({ name: 'John' })
.build();
// query类型为:
// QueryBuilder<'users', 'id' | 'name' | 'email', { name: string }>
运行时类型安全
使用Zod进行Schema验证
// 运行时类型安全
import { z } from 'zod';
// 定义Schema
const UserSchema = z.object({
id: z.number().int().positive(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'user', 'guest']).default('user'),
createdAt: z.date().default(() => new Date()),
metadata: z.record(z.unknown()).optional(),
});
// 推断TypeScript类型
type User = z.infer;
// 验证数据
function parseUser(data: unknown): User {
return UserSchema.parse(data); // 验证失败抛出错误
}
function safeParseUser(data: unknown): User | null {
const result = UserSchema.safeParse(data);
return result.success ? result.data : null;
}
// 异步验证
const AsyncUserSchema = UserSchema.extend({
avatar: z.string().url().refine(
async (url) => {
// 验证URL可访问
const response = await fetch(url, { method: 'HEAD' });
return response.ok;
},
{ message: 'Avatar URL is not accessible' }
),
});
// 转换类型
const UserInputSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.string().transform((val) => parseInt(val, 10)),
});
// 组合Schema
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string(),
});
const UserWithAddressSchema = UserSchema.extend({
address: AddressSchema,
});
// 数组验证
const UserListSchema = z.array(UserSchema);
// 联合类型
const ResultSchema = z.union([
z.object({ success: z.literal(true), data: UserSchema }),
z.object({ success: z.literal(false), error: z.string() }),
]);
// API响应类型
const ApiResponseSchema = (dataSchema: T) =>
z.object({
status: z.number(),
message: z.string(),
data: dataSchema,
});
const UserResponseSchema = ApiResponseSchema(UserSchema);
type UserResponse = z.infer;
branded types(品牌类型)
// 品牌类型:在结构类型系统中实现名义类型
declare const __brand: unique symbol;
type Brand = T & { [__brand]: B };
// 定义品牌类型
type UserId = Brand;
type PostId = Brand;
type Email = Brand;
// 创建品牌类型值
function createUserId(id: string): UserId {
return id as UserId;
}
function createPostId(id: string): PostId {
return id as PostId;
}
// 使用
const userId = createUserId('user-123');
const postId = createPostId('post-456');
// 类型错误!不能混用
function getUser(id: UserId): User { /* ... */ }
getUser(userId); // OK
getUser(postId); // Error: Argument of type 'PostId' is not assignable to parameter of type 'UserId'
// 验证品牌类型
function isEmail(value: string): value is Email {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}
function sendEmail(to: Email, subject: string) {
// 确保to是验证过的Email类型
}
const email = 'test@example.com';
if (isEmail(email)) {
sendEmail(email, 'Hello'); // OK
}
// 更安全的品牌类型模式
type Validated = Brand;
interface FormData {
email: string;
password: string;
}
function validateForm(data: FormData): Validated | null {
if (data.email.includes('@') && data.password.length >= 8) {
return data as Validated;
}
return null;
}
function submitForm(data: Validated) {
// 只能提交验证过的数据
}
设计模式与架构
依赖注入类型安全
// 类型安全的依赖注入
// 服务令牌
type ServiceToken = symbol & { __type: T };
function createToken(name: string): ServiceToken {
return Symbol(name) as ServiceToken;
}
// 容器接口
interface Container {
register(token: ServiceToken, instance: T): void;
resolve(token: ServiceToken): T;
}
// 实现
class ServiceContainer implements Container {
private services = new Map();
register(token: ServiceToken, instance: T): void {
this.services.set(token, instance);
}
resolve(token: ServiceToken): T {
const service = this.services.get(token);
if (!service) {
throw new Error(`Service not found: ${String(token)}`);
}
return service as T;
}
}
// 定义服务
interface IUserService {
getUser(id: string): Promise;
}
interface ILogger {
log(message: string): void;
}
// 创建令牌
const UserServiceToken = createToken('UserService');
const LoggerToken = createToken('Logger');
// 使用
const container = new ServiceContainer();
container.register(LoggerToken, console);
container.register(UserServiceToken, new UserService());
// 类型安全的解析
const userService = container.resolve(UserServiceToken);
const logger = container.resolve(LoggerToken);
事件总线类型安全
// 类型安全的事件总线
// 事件映射
type EventMap = {
'user:login': { userId: string; timestamp: Date };
'user:logout': { userId: string };
'post:created': { postId: string; authorId: string };
'error': { message: string; code: number };
};
// 类型安全的事件总线
class TypedEventBus> {
private listeners: {
[K in keyof Events]?: Set<(payload: Events[K]) => void>;
} = {};
on(
event: K,
listener: (payload: Events[K]) => void
): () => void {
if (!this.listeners[event]) {
this.listeners[event] = new Set();
}
this.listeners[event]!.add(listener);
return () => {
this.listeners[event]!.delete(listener);
};
}
emit(event: K, payload: Events[K]): void {
this.listeners[event]?.forEach(listener => listener(payload));
}
once(
event: K,
listener: (payload: Events[K]) => void
): void {
const onceListener = (payload: Events[K]) => {
listener(payload);
this.off(event, onceListener);
};
this.on(event, onceListener);
}
off(
event: K,
listener: (payload: Events[K]) => void
): void {
this.listeners[event]?.delete(listener);
}
}
// 使用
const eventBus = new TypedEventBus();
// 类型安全的事件监听
eventBus.on('user:login', ({ userId, timestamp }) => {
console.log(`User ${userId} logged in at ${timestamp}`);
});
// 类型安全的事件触发
eventBus.emit('user:login', {
userId: '123',
timestamp: new Date(),
});
// 错误:类型不匹配
eventBus.emit('user:login', {
userId: '123',
// timestamp: 'now', // Error: Type 'string' is not assignable to type 'Date'
});
状态机类型
// 类型安全的状态机
type StateMachineConfig = {
initial: State;
states: {
[S in State]: {
on?: {
[E in Event]?: State | { target: State; action?: () => void };
};
entry?: () => void;
exit?: () => void;
};
};
};
class StateMachine<
State extends string,
Event extends string
> {
private currentState: State;
constructor(private config: StateMachineConfig) {
this.currentState = config.initial;
}
getState(): State {
return this.currentState;
}
send(event: Event): void {
const stateConfig = this.config.states[this.currentState];
const transition = stateConfig.on?.[event];
if (!transition) return;
const targetState = typeof transition === 'string'
? transition
: transition.target;
// 执行exit动作
stateConfig.exit?.();
// 执行transition action
if (typeof transition === 'object') {
transition.action?.();
}
// 切换状态
this.currentState = targetState;
// 执行entry动作
this.config.states[targetState].entry?.();
}
}
// 定义状态机
type TrafficLightState = 'green' | 'yellow' | 'red';
type TrafficLightEvent = 'TIMER' | 'POWER_OUTAGE';
const trafficLightMachine = new StateMachine({
initial: 'green',
states: {
green: {
on: { TIMER: 'yellow' },
entry: () => console.log('Green light!'),
},
yellow: {
on: { TIMER: 'red' },
entry: () => console.log('Yellow light!'),
},
red: {
on: { TIMER: 'green' },
entry: () => console.log('Red light!'),
},
},
});
// 使用
trafficLightMachine.send('TIMER'); // OK
trafficLightMachine.send('POWER_OUTAGE'); // OK(虽然没有定义,但类型允许)
// trafficLightMachine.send('UNKNOWN'); // Error: Argument of type '"UNKNOWN"' is not assignable
架构决策总结
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 运行时验证 | Zod / Yup / io-ts | API响应、表单数据验证 |
| 品牌类型 | branded types | ID区分、验证状态标记 |
| 配置类型 | 模板字面量类型 | 环境变量、路径类型 |
| API类型 | OpenAPI + 代码生成 | 前后端契约一致性 |
| 全局状态 | 类型安全的事件总线 | 模块间通信 |
| 复杂业务逻辑 | 类型安全状态机 | 工作流、订单状态 |
TypeScript最佳实践
- ✅ 启用严格模式:strict: true捕获更多错误
- ✅ 优先使用类型推断:减少显式类型注解
- ✅ 使用unknown而非any:强制类型检查
- ✅ 利用const断言:as const获取最精确类型
- ✅ 类型与实现分离:类型定义在.d.ts文件
- ✅ 避免过度类型体操:可读性优先
总结
TypeScript的类型系统是图灵完备的,能够表达极其复杂的类型关系。但类型的终极目的是服务于代码质量和开发体验,而非炫技。
掌握高级类型技巧后,应该将其应用于实际架构设计:构建类型安全的API层、设计领域模型、实现可靠的依赖注入。让类型成为团队的共同语言,提升整体开发效率和代码可靠性。