TypeScript 类型编程:Brand Types 实战
通过 Brand Types 为原始类型添加语义标记,防止 UserId 和 PostId 混用,让编译器在构建期捕获此类错误。
问题:原始类型太宽松
// 危险:两个 string 参数顺序容易搞反
function assignPost(userId: string, postId: string) { ... }
assignPost(post.id, user.id); // 编译器不报错,运行时出 bug
解决:Brand Types
Brand Types 通过 TypeScript 的结构化类型系统,在运行时相同的类型上添加编译期标记:
type Brand<T, B> = T & { readonly __brand: B };
type UserId = Brand<string, 'UserId'>;
type PostId = Brand<string, 'PostId'>;
function assignPost(userId: UserId, postId: PostId) { ... }
// ✅ 正确用法
const uid = 'u_123' as UserId;
const pid = 'p_456' as PostId;
assignPost(uid, pid);
// ❌ 编译期报错:不能将 PostId 赋给 UserId
assignPost(pid, uid);
实际使用场景
| 场景 | 类型 |
|---|---|
| 用户 ID | Brand<string, 'UserId'> |
| 文章 slug | Brand<string, 'PostSlug'> |
| 金额(分) | Brand<number, 'Cents'> |
| 邮箱 | Brand<string, 'Email'> |
工厂函数
生产代码中通过工厂函数创建 Brand 值,避免手动 as 断言:
function createUserId(raw: string): UserId {
// 这里可以加验证逻辑
return raw as UserId;
}
小结
Brand Types 是零运行时开销的类型安全增强——编译期捕获语义错误,生产 JS 输出与原始代码无异。