名义化类型

TypeScript 的类型系统是结构化的,这里有个主要的原因。然而,在实际项目中可能是你想区分两个类型不同的变量,尽管它们具有相同的结构。一个非常常见的实际用例是身份的验证(它们可能只是在 C# 或者 Java 中表示一个它们语义化名字的字符串)。

这有一些社区推荐的方式,我按照个人爱好降序排列:

使用字面量类型

这种模式使用泛型和字面量类型:

  1. // 泛型 Id 类型
  2. type Id<T extends string> = {
  3. type: T;
  4. value: string;
  5. };
  6. // 特殊的 Id 类型
  7. type FooId = Id<'foo'>;
  8. type BarId = Id<'bar'>;
  9. // 可选:构造函数
  10. const createFoo = (value: string): FooId => ({ type: 'foo', value });
  11. const createBar = (value: string): BarId => ({ type: 'bar', value });
  12. let foo = createFoo('sample');
  13. let bar = createBar('sample');
  14. foo = bar; // Error
  15. foo = foo; // Okey
  • 优点
    • 不需要类型断言。
  • 缺点
    • 如上结构 {type,value} 可能不那么尽如人意,而且需要服务器序列化支持。

使用枚举

TypeScript 中枚举 提供一定程度的名义化类型。如果两个枚举的命名不相同,则它们类型不相等。我们可以利用这个事实来为结构上兼容的类型,提供名义化类型。

解决办法包括:

  • 创建一个只有名字的枚举;
  • 利用这个枚举与实际结构体创建一个交叉类型(&)。
    如下所示,当实际结构体仅仅是一个字符串时:
  1. // FOO
  2. enum FooIdBrand {};
  3. type FooId = FooIdBrand & string;
  4. // BAR
  5. enum BarIdBrand {};
  6. type BarId = BarIdBrand & string;
  7. // user
  8. let fooId: FooId;
  9. let barId: BarId;
  10. // 类型安全
  11. fooId = barId; // error
  12. barId = fooId; // error
  13. // 创建一个新的
  14. fooId = 'foo' as FooId;
  15. barId = 'bar' as BarId;
  16. // 两种类型都与基础兼容
  17. let str: string;
  18. str = fooId;
  19. str = barId;

使用接口

因为 number 类型与 enum 类型在类型上是兼容的,因此我们不能使用上述提到的方法来处理它们。取而代之,我们可以使用接口打破这种类型的兼容性。TypeScript 编译团队仍然在使用这种方法,因此它值得一提。使用 _ 前缀和 Brand 后缀是一种我强烈推荐的惯例方法(TypeScript 也这么推荐)。

解决办法包括:

  • 在类型上添加一个不用的属性,用来打破类型兼容性;
  • 在需要的时候使用类型断言。
    如下所示:
  1. // FOO
  2. interface FooId extends String {
  3. _fooIdBrand: string; // 防止类型错误
  4. }
  5. // BAR
  6. interface BarId extends String {
  7. _barIdBrand: string; // 防止类型错误
  8. }
  9. // 使用
  10. let fooId: FooId;
  11. let barId: BarId;
  12. // 类型安全
  13. fooId = barId; // error
  14. barId = fooId; // error
  15. fooId = <FooId>barId; // error
  16. barId = <BarId>fooId; // error
  17. // 创建新的
  18. fooId = 'foo' as any;
  19. barId = 'bar' as any;
  20. // 如果你需要以字符串作为基础
  21. var str: string;
  22. str = fooId as any;
  23. str = barId as any;

原文: https://jkchao.github.io/typescript-book-chinese/tips/nominalTyping.html