枚举

简单枚举

enum Tristate {
    False,
    True,
    Unknown,
    Never,
  }

编译成JS:

  var Tristate;
(function(Tristate) {
  Tristate[(Tristate['False'] = 0)] = 'False';
  Tristate[(Tristate['True'] = 1)] = 'True';
  Tristate[(Tristate['Unknown'] = 2)] = 'Unknown';
})(Tristate || (Tristate = {}));

结果:

Tristate['False] // default: 0
Tristate['True] // 1
Tristate['Unknow] // 2
Tristate['Never] // 3

数字枚举和字符串枚举

enum Tristate {
    False,
    True = 'TRUE',
    Unknown = 2,
    Never
  }
  
// 结果
Tristate['False] // default:0
Tristate['True] // 'TRUE'
Tristate['Unknow] // 2
Tristate['Never] // 3

开放枚举

enum Color {
  Red,
  Green,
  Blue
}

enum Color {
  DarkRed = 3,
  DarkGreen,
  DarkBlue
}

Color:

{
  Red,
  Green,
  Blue
  DarkRed = 3,
  DarkGreen,
  DarkBlue
}

重复枚举的字段前面的被后面的覆盖,不重复枚举的会合并成一个枚举类型

辨析联合类型

TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。

这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。

1.可辨识

可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:

enum CarTransmission {
  Automatic = 200,
  Manual = 300
}

interface Motorcycle {
  vType: "motorcycle"; // discriminant
  make: number; // year
  after:string;
}

interface Car {
  vType: "car"; // discriminant
  transmission: CarTransmission;
  after:string;
}

interface Truck {
  vType: "truck"; // discriminant
  capacity: number; // in tons
  after:string;
}

在上述代码中,我们分别定义了 MotorcycleCarTruck 三个接口,在这些接口中都包含一个 vType 属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。

也可以根据接口中的特殊字段来辨析,例如Motorcyclemake属性,CartransmissionTruckcapacity

2.联合类型

基于前面定义了三个接口,我们可以创建一个 Vehicle 联合类型:

type Vehicle = Motorcycle | Car | Truck;

现在我们就可以开始使用 Vehicle 联合类型,对于 Vehicle 类型的变量,它可以表示不同类型的车辆。

3.不同表现

// 可辨析 公共字段vType值不同
  const vehicle:Vehicle = {
      vType:'motorcycle' // 推断为 Motorcycle
  }
  
// 可辨析 独一无二的字段 make
  const vehicle:Vehicle = {
     make:123 // 推断为 Motorcycle
  }
  
// 不可辨析,需要自己断言
  const vehicle:Vehicle = {
     after:'after'
  }

4.类型守卫

下面我们来定义一个 evaluatePrice 方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下:

const EVALUATION_FACTOR = Math.PI; 

function evaluatePrice(vehicle: Vehicle) {
  return vehicle.capacity * EVALUATION_FACTOR;
}

const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);

对于以上代码,TypeScript 编译器将会提示以下错误信息:

Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'.

原因是在 Motorcycle 接口中,并不存在 capacity 属性,而对于 Car 接口来说,它也不存在 capacity 属性。那么,现在我们应该如何解决以上问题呢?这时,我们可以使用类型守卫。下面我们来重构一下前面定义的 evaluatePrice 方法,重构后的代码如下:

function evaluatePrice(vehicle: Vehicle) {
  switch(vehicle.vType) {
    case "car":
      return vehicle.transmission * EVALUATION_FACTOR;
    case "truck":
      return vehicle.capacity * EVALUATION_FACTOR;
    case "motorcycle":
      return vehicle.make * EVALUATION_FACTOR;
  }
}

在以上代码中,我们使用 switchcase 运算符来实现类型守卫,从而确保在 evaluatePrice 方法中,我们可以安全地访问 vehicle 对象中的所包含的属性,来正确的计算该车辆类型所对应的价格。

交叉类型

同名基础类型属性的合并

接口 X 和接口 Y 都含有一个相同的成员 c,但它们的类型不一致。对于这种情况,此时 XY类型中成员 c 的类型是不是可以是 stringnumber 类型呢?

interface X {
  c: string;
  d: string;
}

interface Y {
  c: number;
  e: string
}

type XY = X & Y; // Never

混入后成员 c 的类型为 string & number,即成员 c 的类型既可以是 string 类型又可以是 number 类型。很明显这种类型是不存在的,所以混入后成员 c 的类型为 never

同名非基础类型属性的合并

interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true,
    e: 'semlinker',
    f: 666
  }
};

console.log('abc:', abc);

在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。

函数重载

interface Overloaded {
  (foo: string): string;
  (foo: number): number;
}

// 实现接口的一个例子:
function stringOrNumber(foo: number): number;
function stringOrNumber(foo: string): string;
function stringOrNumber(foo: any): any {
  if (typeof foo === 'number') {
    return foo * foo;
  } else if (typeof foo === 'string') {
    return `hello ${foo}`;
  }
}

const overloaded: Overloaded = stringOrNumber;

// 使用
const str = overloaded(''); // str 被推断为 'string'
const num = overloaded(123); // num 被推断为 'number'

接口扩展

简单接口

interface Square {
  kind: 'square';
  size: number;
}

通用接口

interface DictionaryString<T>{
  [index:string]:T
}

具有某些字段

interface DictionaryStringWithName{
   name:string;
  [index:string]:string
}

下面的会出错:

interface DictionaryString{
   name:string;
  [index:string]number; // 与name类型不一致,需要兼容name
}

需要兼容name:

interface DictionaryString{
   name:string;
  [index:string]: number | string; // ok
}

高级用法

https://www.typescriptlang.or...

泛型约束

确保属性存在

有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。

一个很好的例子是在处理字符串或数组时,我们会假设 length 属性是可用的。让我们再次使用 identity 函数并尝试输出参数的长度:

function identity<T>(arg: T): T {
  console.log(arg.length); // Error
  return arg;
}
复制代码

在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口,比如这样:

interface Length {
  length: number;
}

function identity<T extends Length>(arg: T): T {
  console.log(arg.length); // 可以获取length属性
  return arg;
}

检查对象上的键是否存在

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number

通过 keyof 操作符,我们就可以获取指定类型的所有键,之后我们就可以结合前面介绍的 extends 约束,即限制输入的属性名包含在 keyof 返回的联合类型中。具体的使用方式如下:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

高级用法实现

前置学习

infer

infer 用于构造函数类型中,可用于参数位置 new (...args: infer P) => any; 和返回值位置 new (...args: any[]) => infer P;

type ParamType<T> = T extends (param: infer P) => any ? P : T;

typeof

// Readonly
type Readonly<T, K in keyof T> = {
    readonly K: T[K];
};

// Require
type Require<T>{
  [K in keyof T]-?:T[K];
}

// Partial
type Partial<T> = {
    [P in keyof T]?: T[P];
};

// Record
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

// Pick
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

// Omit
type Omit<T,K> = Pick<T, Exclude<keyof T, K>>

// Exclude
type Exclude<T, U> = T extends U ? never : T;

// Extract
type Extract<T,U> = T extends U ? T : never;

// RetureType
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

可以参考:https://github.com/chenxiaoch...

FQA

interface vs type

1.Objects/Functions

接口和类型别名都可以用来描述对象的形状或函数签名:

接口

interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}

类型别名

type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;
2.Other Types

与接口类型不一样,类型别名可以用于一些其他类型,比如原始类型、联合类型和元组:

// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];
3.Extend

接口和类型别名都能够被扩展,但语法有所不同。此外,接口和类型别名不是互斥的。接口可以扩展类型别名,而反过来是不行的。

Interface extends interface

interface PartialPointX { x: number; }
interface Point extends PartialPointX { 
  y: number; 
}

Type alias extends type alias

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

Interface extends type alias

type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

Type alias extends interface

interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };
4.Implements

类可以以相同的方式实现接口或类型别名,但类不能实现使用类型别名定义的联合类型:

interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  x = 1;
  y = 2;
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x = 1;
  y = 2;
}

type PartialPoint = { x: number; } | { y: number; };

// A class can only implement an object type or 
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
  x = 1;
  y = 2;
}
5.Declaration merging

与类型别名不同,接口可以定义多次,会被自动合并为单个接口。

interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };

any vs unknown

共同点

就像所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown

let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

不同点

  • unknown 类型只能被赋值给 any 类型和 unknown 类型本身,any 可以赋值给任何类型。
  • value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。
let value: unknown;

value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

void vs never

void 表示没有任何类型,never 表示永远不存在的值的类型。

共同点

void 和 never 都表示函数没有返回值。

不同点

  • void的语义是指返回了空值,never是指函数永不返回或者总是抛出错误
  • void 类型可以被赋值(在 strictNullChecking 为 false 时),但是除了 never 本身以外,其他任何类型不能赋值给 never。

specialcoder
2.2k 声望170 粉丝

前端 设计 摄影 文学