Skip to content
☆´∀`☆
On this page

类型系统

一组为变量、函数等结构分配、实施类型的规则,通过显式地指定或类型推导来分配类型。同时类型系统也定义了如何判断类型之间的兼容性:在 TypeScript 中即是结构化类型系统。

结构化类型系统

typescrpt 比较两个类型并非通过类型的名称,而是比较这两个类型上实际拥有的属性与方法:

ts
class Cat{
  move () {}
}
class Dog{
  move () {}
}

const mm: Cat = new Dog()

结构化类型有个别称叫鸭子类型。即如果你看到一只鸟走起来像鸭子,游泳像鸭子,叫得也像鸭子,那么这只鸟就是鸭子。

如果在 Dog 上添加一个独有成员,也不会报错。因为结构化类型系统会认为 Dog 类完全实现了 Cat 类。

ts
class Cat{
  move () {}
}
class Dog{
  move () {}
  eat () {}
}

const mm: Cat = new Dog()

标准类型系统

标准类型系统是指类型系统会根据类型名称进行比较。如果类型名称不同,即使类型上的属性与方法完全一致,也会报错。C++、Java、Rust 等语言中都主要使用标称类型系统。

但是在 ts 中,无法直接实现:

ts
type USD = number;
type CNY = number;

function addMoney (usd: USD, cny: CNY) {
  return usd + cny
}

const usd: USD = 100

const cny: CNY = 200

addMoney(cny, usd) // success

在 TypeScript 中模拟标称类型系统

类型的重要意义之一是限制了数据的可用操作与实际意义。这往往是通过类型附带的额外信息来实现的(类似于元数据),要在 TypeScript 中实现,其实我们也只需要为类型额外附加元数据即可。

就是,给类型打个 tag。

ts
export declare class TagProtector<T extends string>{
  protected __tag__: T
}
export type Nominal<T, U extends string> = T & TagProtector<U>;

TagProtector声明一个带 tag 的类,然后用Nominal将类型与 tag 关联起来。这样,我们就可以通过Nominal来模拟标称类型系统了。

ts
type USD = Nominal<number, 'usd'>;
type CNY = Nominal<number, 'cny'>;

const usd: USD = 100 as USD

const cny: CNY = 200 as CNY

function addMoney (usd: USD, cny: CNY) {
  return (usd + cny) as CNY
}

addMoney(cny, usd) 

addMoney(usd, cny) // success

或者,在类上打 tag,可以在运行时添加更多的检查逻辑,同时在类型层面也得到了保障。

typescript
class USD {
  private __tag!: void;
  constructor(public value: number) {}
}
class CNY {
  private __tag!: void;
  constructor(public value: number) {}
}

const usd = new USD(100);

const cny = new CNY(100);

function addMoney(usd: USD, cny: CNY) {
  return usd.value + cny.value;
}

addMoney(cny, usd); 
addMoney(usd, cny); // success