Typescript

literal type & type expression

literal type 由于能够表示一些基本值,配合 extends (相当于 flow control 的 if else),infer(extractType) 和 type constructor(type / interface 表示函数),可以组合成基本的 表达式,也就是type expression。可以进行type-level programming。
image.png
如基本的取值:

  1. type Head<T extends any[]> = T[0] // lookup type
  2. type Length<T extends any[]> = T['length']
  3. type Tail<T> = T extends (head: any, ...tail: infer U) ? U : never;
  4. type Length<T extends any[]> = T extends any[] & {
  5. length: infer L;
  6. }
  7. ? L
  8. : never;
  9. export type LengthMinusOne<T extends any[]> = Length<Tail<T>>;
  10. export type LengthPlusOne<T extends any[]> = Length<Cons<any, T>>;
  11. export type Last<T extends any[]> = T[LengthMinusOne<T>];
  12. type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

Type-level programming(类型体操)的思路仍然和一般编程一样,思考如何通过组合来表达出更多逻辑。两种组合的区别只是它们发生的时机,一个是编译时一个是运行时。而组合的技巧能有多强,就是看对类型系统有多了解了,就如同我们对运行平台(如浏览器,JS引擎,操作系统)有多了解一样。

nominal type

由于 ts 采用了 structural typing
https://github.com/Microsoft/TypeScript/wiki/FAQ#what-is-structural-typing
The idea behind structural typing is that two types are compatible if their members are compatible.
所以两个 类型 的可兼容性,是基于他们的内部成员可兼容的。
例外:
类的私有属性:https://github.com/Microsoft/TypeScript/wiki/FAQ#when-and-why-are-classes-nominal

但是往往现实是存在大量实现结构相同,但意义不同的两个变量,此时在不改变真实结构的情况下可以在类型层面添加 id(一般被称为 branding)。

以及为了实现 nominal typing 可以采取如下的操作:
利用 ts 检查类型 成员 的特性,如下为一个 string 类型合并一个自有的标签(branding)

  1. type SomeUrl = string & {'this is a url': {}};
  2. type FirstName = string & {'person name': {}};
  3. // Add type assertions
  4. let x1 = '' as SomeUrl;
  5. let y1 = 'bob' as FirstName;
  6. x1 = y1; // Error

更加详细的解释:https://github.com/microsoft/TypeScript/issues/42939#issuecomment-785316256 以及 ts 官方进行的为类型添加标记的 pr:https://github.com/microsoft/TypeScript/pull/33038

通过类型断言可以对变量进行类型信息的拓展,所以对于简单类型的拓展,灵活使用断言可以在让类型系统获得更多额外信息,但对于函数体这种除了自身类型还要考虑内部运行中逻辑的情况,使用断言就需要谨慎了。