由于 TypeScript 采用结构类型,UserId 和 OrderId 如果都是 string 就可以互换——编译器无法阻止你混淆它们。Branded types 通过附加唯一的、phantom "brand" 来模拟名义类型,使得原本相同的类型变得不同。
ts
<T, B> = T & { : B };
= <, >;
= <, >;
() { id; }
uid = ;
oid = ;
(uid);
(oid);
();
由于 TypeScript 采用结构类型,UserId 和 OrderId 如果都是 string 就可以互换——编译器无法阻止你混淆它们。Branded types 通过附加唯一的、phantom "brand" 来模拟名义类型,使得原本相同的类型变得不同。
<T, B> = T & { : B };
= <, >;
= <, >;
() { id; }
uid = ;
oid = ;
(uid);
(oid);
();
__brand 属性在运行时永远不存在(值只是一个字符串),但在类型系统中,它使 UserId 和 OrderId 不兼容——防止了一整类混淆错误。
function toUserId(s: string): UserId {
if (!s.startsWith("u")) throw new Error("invalid UserId");
return s as UserId; // validation happens here, once
}
在边界处验证一次;代码的其余部分信任 branded 类型。
ID(UserId vs OrderId)、验证/清理的字符串(Email、SafeHtml)、单位(Meters vs Feet)、PositiveNumber ——只要两个值共享一个原始类型但不应混淆的任何地方。
Branded types 在结构类型过于宽松的地方增加名义安全性。
它们在编译时捕获参数顺序混淆和"未验证 vs 已验证"混淆,零运行时成本——这是大型、正确性至关重要的代码库中领域建模的强大模式。