Type compatibility in TypeScript is based in structural subtyping(aka, duck typing), contrast with nominal typing (in C# or Java)
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
// 如果在 C# 或者 Java 下这样就 gg 了
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
interface Named {
name: string;
}
let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: "Alice", location: "Seattle" };
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
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
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 的用法:
let items = [1, 2, 3];
// Don't force these extra parameters
items.forEach((item, index, array) => console.log(item));
// Should be OK!
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
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
status = Color.Green; // Error
Classes
Static members and constructors do not affect compatibility
class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
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
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y; // OK, because y matches structure of x
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // Error, because x and y are not compatible
// 没标明 type arguments, 会被当成 any
let identity = function<T>(x: T): T {
// ...
}
let reverse = function<U>(y: U): U {
// ...
}
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 , 现在还看不起, 看也没什么卵用, 用起来才是真的