Loading articles...
TypeScript 的类型系统是图灵完备的。这意味着你可以在类型层面实现复杂的逻辑。但能力越大,责任越大——过度复杂的类型会让代码难以维护。本文分享几个在实际项目中真正有用的高级类型技巧。
条件类型是最常用的高级类型工具,它根据条件选择不同的类型:
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<"hello">; // "yes"
type B = IsString<42>; // "no"
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
// 递归展开嵌套 Promise
type Result = Awaited<Promise<Promise<string>>>; // string
TypeScript 4.1 引入的模板字面量类型让我们可以在类型层面拼接字符串:
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
// 结合联合类型的分布式特性
type CSSProperty = "margin" | "padding";
type CSSDirection = "Top" | "Right" | "Bottom" | "Left";
type CSSSpacing = `${CSSProperty}${CSSDirection}`;
// "marginTop" | "marginRight" | ... | "paddingLeft"
处理树形结构时,递归类型非常有用:
interface TreeNode<T> {
value: T;
children?: TreeNode<T>[];
}
// 递归深度遍历类型
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? DeepReadonly<T[K]>
: T[K];
};
自定义类型守卫让运行时检查与类型系统配合:
interface User {
type: "user";
name: string;
email: string;
}
interface Admin {
type: "admin";
name: string;
permissions: string[];
}
type Account = User | Admin;
function isAdmin(account: Account): account is Admin {
return account.type === "admin";
}
// 使用 discriminated union
function handleAccount(account: Account) {
if (isAdmin(account)) {
// 这里 account 被收窄为 Admin
console.log(account.permissions);
}
}
下面是几个可以直接复用的类型工具:
| 工具类型 | 用途 |
|---|---|
Nullable<T> |
将 T 的所有属性变为可空 |
PickByType<T, V> |
选取指定类型的属性 |
DeepPartial<T> |
递归 Partial |
Brand<T, B> |
名义类型标记 |
// Brand type 防止原始类型混淆
type Brand<T, B extends string> = T & { __brand: B };
type UserId = Brand<string, "UserId">;
type PostId = Brand<string, "PostId">;
function getUser(id: UserId) { /* ... */ }
const userId = "abc" as UserId;
const postId = "abc" as PostId;
getUser(userId); // OK
getUser(postId); // Type error!
加载评论…