Skip to content
Created: October 8, 2020

Structural type system ​

1. Interface compatibility ​

  • If the incoming variable does not match the declared type, TS will perform a compatibility check
  • The principle is Duck-Check, which means that as long as the attribute variables declared in the target type exist in the source type, they are compatible.
ts
interface Animal {
  name: string;
  age: number;
}

interface Person {
  name: string;
  age: number;
  gender: number;
}
// 要判断目标类型`Person`是否能够兼容输入的源类型`Animal`
function getName(animal: Animal): string {
  return animal.name;
}

let p = {
  name: "hello ",
  age: 10,
  gender: 0,
};

getName(p);
//只有在传参的时候两个变量之间才会进行兼容性的比较,赋值的时候并不会比较,会直接报错
let a: Animal = {
  name: "hello ",
  age: 10,
  gender: 0,
};

2. Basic type compatibility ​

ts
//基本数据类型也有兼容性判断
let num: string | number;
let str: string = "hello ";
num = str;

//只要有toString()方法就可以赋给字符串变量
let num2: {
  toString(): string;
};

let str2: string = "jiagou";
num2 = str2;

3. Class Compatibility ​

  • In TS, it is a structural type system, which only compares structures and does not care about types.
ts
class Animal {
  name: string;
}
class Bird extends Animal {
  swing: number;
}

let a: Animal;
a = new Bird();

let b: Bird;
//并不是父类兼容子类,子类不兼容父类
b = new Animal();
ts
class Animal {
  name: string;
}
//如果父类和子类结构一样,也可以的
class Bird extends Animal {}

let a: Animal;
a = new Bird();

let b: Bird;
b = new Animal();
ts
//甚至没有关系的两个类的实例也是可以的
class Animal {
  name: string;
}
class Bird {
  name: string;
}
let a: Animal;
a = new Bird();
let b: Bird;
b = new Animal();

4. Function compatibility ​

  • When comparing functions, you must first compare the parameters of the function, and then compare the return value of the function.

4.1 Comparison parameters ​

ts
type sumFunc = (a: number, b: number) => number;
let sum: sumFunc;
function f1(a: number, b: number): number {
  return a + b;
}
sum = f1;

//可以省略一个参数
function f2(a: number): number {
  return a;
}
sum = f2;

//可以省略二个参数
function f3(): number {
  return 0;
}
sum = f3;

//多一个参数可不行
function f4(a: number, b: number, c: number) {
  return a + b + c;
}
sum = f4;

4.2 Compare return values ​

ts
type GetPerson = () => { name: string; age: number };
let getPerson: GetPerson;
//返回值一样可以
function g1() {
  return { name: "hello ", age: 10 };
}
getPerson = g1;
//返回值多一个属性也可以
function g2() {
  return { name: "hello ", age: 10, gender: "male" };
}
getPerson = g2;
//返回值少一个属性可不行
function g3() {
  return { name: "hello " };
}
getPerson = g3;
//因为有可能要调用返回值上的方法
getPerson().age.toFixed();

5. Covariance and contravariance of functions ​

  • Covariant: only in the same direction;

  • Contravariant: Only in the opposite direction;

  • Bidirectional covariance (Bivariant): including the same direction and different directions;

  • Invariant: If types are not identical, they are incompatible.

  • A ≼ B means A is a subtype of B.

  • A → B refers to the function type with A as the parameter type and B as the return value type.

  • x : A means x is of type A

  • The return value type is covariant, while the parameter type is contravariant

  • The return value type can be passed to the subclass, and the parameters can be passed to the parent class.

  • Parameters are inverse to the parent class and return values are covariant to the subclass. Help your father and return the shoes.

ts
class Animal {}
class Dog extends Animal {
  public name: string = "Dog";
}
class BlackDog extends Dog {
  public age: number = 10;
}
class WhiteDog extends Dog {
  public home: string = "北京";
}
let animal: Animal;
let blackDog: BlackDog;
let whiteDog: WhiteDog;
type Callback = (dog: Dog) => Dog;
function exec(callback: Callback): void {
  callback(whiteDog);
}
//不行  callback(redDog);
type ChildToChild = (blackDog: BlackDog) => BlackDog;
const childToChild: ChildToChild = (blackDog: BlackDog): BlackDog => blackDog;
exec(childToChild);

//也不行,理由同上
type ChildToParent = (blackDog: BlackDog) => Animal;
const childToParent: ChildToParent = (blackDog: BlackDog): Animal => animal;
exec(childToParent);

//不行 因为有可能调用返回的Dog的方法
type ParentToParent = (animal: Animal) => Animal;
const parentToParent: ParentToParent = (animal: Animal): Animal => animal;
exec(parentToParent);

//可以,所有的狗都是动物,返回的不管什么狗都是狗
type ParentToChild = (animal: Animal) => BlackDog;
const parentToChild: ParentToChild = (animal: Animal): BlackDog => blackDog;
exec(parentToChild);
//(Animal → Greyhound) ≼ (Dog → Dog)
//返回值类型很容易理解:黑狗是狗的子类。但参数类型则是相反的:动物是狗的父类!
ts
// string | number|boolean 是 string | number的父类型
// string是string|number的子类型
type Callback2 = (a: string | number) => string | number;
function exec2(callback: Callback2): void {
  callback("");
}
type ParentToChild2 = (a: string | number | boolean) => string;
const parentToChild2: ParentToChild2 = (a: string | number | boolean): string =>
  "";
exec2(parentToChild2);

type Callback3 = (a: string | number) => string | number;
function exec3(callback: Callback2): void {
  callback("");
}
type ParentToParent3 = (a: string) => string;
const parentToParent3: ParentToParent3 = (a: string): string => "";
exec3(parentToChild3);
  • In TypeScript, parameter types are bidirectionally covariant, which means they are both covariant and contravariant, which is not safe.But now you can fix this problem in TypeScript 2.6 version via --strictFunctionTypes or --strict flag

6. Generic compatibility ​

  • When judging compatibility, generics will first judge the specific type, and then judge the compatibility.
ts
//接口内容为空没用到泛型的时候是可以的
//1.接口内容为空没用到泛型的时候是可以的
interface Empty<T> {}
let x!: Empty<string>;
let y!: Empty<number>;
x = y;

//2.接口内容不为空的时候不可以
interface NotEmpty<T> {
  data: T;
}
let x1!: NotEmpty<string>;
let y1!: NotEmpty<number>;
x1 = y1;

//实现原理如下,称判断具体的类型再判断兼容性
interface NotEmptyString {
  data: string;
}

interface NotEmptyNumber {
  data: number;
}
let xx2!: NotEmptyString;
let yy2!: NotEmptyNumber;
xx2 = yy2;

7. Enum compatibility ​

  • Enumerated types are compatible with numeric types, and numeric types are compatible with enumerated types
  • Different enum types are incompatible
ts
//数字可以赋给枚举
enum Colors {
  Red,
  Yellow,
}
let c: Colors;
c = Colors.Red;
c = 1;
c = "1";

//枚举值可以赋给数字
let n: number;
n = 1;
n = Colors.Red;