Type compatibility in TypeScript is based in structural subtyping(aka, duck typing), contrast with nominal typing (in C# or Java)

  1. interface Named {
  2. name: string;
  3. }
  4. class Person {
  5. name: string;
  6. }
  7. let p: Named;
  8. // OK, because of structural typing
  9. // 如果在 C# 或者 Java 下这样就 gg 了
  10. p = new Person()

ts 因为是 js 的超集, js 里对象字面量和匿名函数满天飞, 选择 duck typing 而不是 nominal typing 最正常不过了

Starting out

The basic rule for TypeScript’s structural type system is that x is compatible with y if y has at least the same members as x

  1. interface Named {
  2. name: string;
  3. }
  4. let x: Named;
  5. // y's inferred type is { name: string; location: string; }
  6. let y = { name: "Alice", location: "Seattle" };
  7. x = y;

Note that y has an extra location property, but this does not create an error. Only members of the target type (Named in this case) are considered when checking for compatibility.

This comparison process proceeds recursively, exploring the type of each member and sub-member.

Comparing two functions

  1. let x = (a: number) => 0;
  2. let y = (b: number, s: string) => 0;
  3. y = x; // OK
  4. x = y; // Error

To check if x is assignable to y, we first look at the parameter list. Each parameter in x must have a corresponding parameter in y with a compatible type. Note that the names of the parameters are not considered, only their types. In this case, every parameter of x has a corresponding compatible parameter in y, so the assignment is allowed.

The second assignment is an error, because y has a required second parameter that x does not have, so the assignment is disallowed.

y = x 这种情况之所以被允许, 当然是为了写得更爽和兼容 js 啊, 想一想 forEach 的用法:

  1. let items = [1, 2, 3];
  2. // Don't force these extra parameters
  3. items.forEach((item, index, array) => console.log(item));
  4. // Should be OK!
  5. items.forEach(item => console.log(item));

Function Parameter Bivariance

看不懂

Optional Parameters and Rest Parameters

看不懂, 反正和上面的情况都是说一些边界情况, 能否把一个有些微变形的类型 B 认为是类型 A, compatibility

Fucntions with overloads

Enums

Enums are compatible with numbers, and numbers are compatible with enums
Enum values from different enum types are considered incompatible

  1. enum Status { Ready, Waiting };
  2. enum Color { Red, Blue, Green };
  3. let status = Status.Ready;
  4. status = Color.Green; // Error

Classes

Static members and constructors do not affect compatibility

  1. class Animal {
  2. feet: number;
  3. constructor(name: string, numFeet: number) { }
  4. }
  5. class Size {
  6. feet: number;
  7. constructor(numFeet: number) { }
  8. }
  9. let a: Animal;
  10. let s: Size;
  11. a = s; // OK, or `s = a;` is OK, because if `constructor` on the static side

private and protected members in classes

Private and protected members in a class affect their compatibility

Generics

  1. interface Empty<T> {
  2. }
  3. let x: Empty<number>;
  4. let y: Empty<string>;
  5. x = y; // OK, because y matches structure of x
  6. interface NotEmpty<T> {
  7. data: T;
  8. }
  9. let x: NotEmpty<number>;
  10. let y: NotEmpty<string>;
  11. x = y; // Error, because x and y are not compatible
  12. // 没标明 type arguments, 会被当成 any
  13. let identity = function<T>(x: T): T {
  14. // ...
  15. }
  16. let reverse = function<U>(y: U): U {
  17. // ...
  18. }
  19. identity = reverse; // OK, because (x: any) => any matches (y: any) => any

Advanced Topics

In TypeScript, there are two kinds of compatibility: subtype and assignment
These differ only in that assignment extends subtype compatibility with rules to allow assignment to and from any, and to and from enum with corresponding numeric values

更多的细节要看 ts 的 spec , 现在还看不起, 看也没什么卵用, 用起来才是真的