TypeScript 学习笔记(二)

· 1062 words · 6 min

Literal Type

字面量(literal)用于在代码中表示一个固定值(区别于变量,变量是可变化的),TypeScript 中也有字面量类型。分为 string 字面量类型、number 字面量类型、boolean 字面量类型等。 一般情况下,字面量类型可以看做是对应的类型的子类型。比如 string 字面量类型可以认为是 string 类型的子类型。在这种前提下我们可以得知,字面量类型是可以赋值给对应父类型的,反之则不行(除非用 as)。

let hello: string = ''; // string type
let ts: 'TypeScript' = 'TypeScript'; // string literal type

hello = ts; // OK
ts = hello; // Error: Type '"TypeScript"' is not assignable to type 'string'

TypeScript 中,如果一个字面量是以 const 关键字声明的,那么在使用 typeof 获取类型的时候,返回的是字面量类型。而以 let、var 等关键字声明的字面量,typeof 返回的类型是字面量的父类型。这和 const 的本质禁止再次修改赋值是一一对应的,因此 TypeScript 能更加精确的知道其类型。literal narrow 即字面量收窄(缩小范围)。

  let string1 = "HelloWorld1"; // 变量
const string2 = "HelloWorld2"; // 字面量

type HelloWorld1 = typeof string1; // 'HelloWorld'
type HelloWorld2 = typeof string2; // string

Enum

枚举可以方便地当做字典使用,因为在编译为 JavaScript 之后,枚举转换为了一个同名的对象。若枚举值是数字,那么还可以用作 reflect,利用 value 来获取 key。

enum Animal {
  Dog,
  Cat,
}

console.log(Animal.Dog); // 0
console.log(Animal[1]); // 'Cat'

枚举值为数字时,编译为 JavaScript 之后的结果如下,可以看出额外做了 Animal[0] = 'Dog'Animal[1] = 'Cat' 的赋值操作。

"use strict";
var Animal;
(function (Animal) {
    Animal[Animal["Dog"] = 0] = "Dog";
    Animal[Animal["Cat"] = 1] = "Cat";
})(Animal || (Animal = {}));

利用与 enum 同名的 namespace 我们可以在枚举上绑定一些方法。

enum Animal {
  Dog,
  Cat,
}

namespace Animal {
  export function isCat(animal: Animal): animal is Animal.Cat {
    return animal === Animal.Cat;
  }

  export function isDog(animal: Animal): animal is Animal.Dog {
    return animal === Animal.Dog;
  }
}

const animal = { name: 'name', type: 1 };

在通常情况下,我们使用枚举的时候需要先判断枚举值,然后再做相应的操作。

if(animal.type === Animal.Cat) {
  // do something with cat
}
if(animal.type === Animal.Dog) {
  // do something with dog
}

但是使用 namespace 后,我们可以直接调用枚举值对应的方法,这样代码更加清晰。

if(Animal.isCat(animal.type)) {
  // do something with cat
}
if(Animal.isDog(animal.type)) {
  // do something with dog
}

带有 namespace 的 enum 编译为 JavaScript 之后的结果如下。可以看出额外给 Animal 变量上挂载了 isCat 和 isDog 两个方法。

"use strict";
var Animal;
(function (Animal) {
    Animal[Animal["Dog"] = 0] = "Dog";
    Animal[Animal["Cat"] = 1] = "Cat";
})(Animal || (Animal = {}));
(function (Animal) {
    function isCat(animal) {
        return animal === Animal.Cat;
    }
    Animal.isCat = isCat;
    function isDog(animal) {
        return animal === Animal.Dog;
    }
    Animal.isDog = isDog;
})(Animal || (Animal = {}));

Class Type

当我们声明一个 class 时,其实同时声明了两个类型,一个代表着实例的类型(class),一个代表着构造函数的类型(typeof class)。

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}

const greeter1 = new Greeter('TypeScript');
type Greeter1 = typeof greeter1; // Greeter

const greeter2 = Greeter;
type Greeter2 = typeof greeter2; // typeof Greeter

keyof、typeof

使用 typeof 可以获得变量的类型。typeof 不能直接跟类型,否则报错。

const arr = [1, 'abc'];
type ArrType = typeof arr; // (string | number)[]

type tuple = [1, 'abc'];
type TupleType = typeof tuple; // Error: 'tuple' only refers to a type, but is being used as a value here.

使用 keyof 可以获取到由 interface/type 中的 key 组成的联合类型。

interface Interface {
  a: number;
  b: string;
  c: boolean;
}

type Type = {
  a: number;
  b: string;
  c: boolean;
}

type KeyType1 = keyof Interface; // 'a' | 'b' | 'c'
type KeyType2 = keyof type; // 'a' | 'b' | 'c'

获取一个数组中元素的类型可以使用 typeof, 获取 tuple/array 类型中的每一项的类型可以用类似数组索引的方式。

const array = [1, 'abc'];
type ArrayType = typeof arr; // (string | number)[]
type ItemTypeInArray = ArrayType[number]; // string | number

type tuple = [1, 'abc'];
type ItemTypeInTuple = tuple[number]; // 1 | 'abc' 

当索引签名的 key 是 string 时,keyof 获取到的类型是 string | number,但是当 key 是 number 的时候,keyof 获取到的类型是 number

interface InterfaceWithStringKey {
  [key: string]: any; 
}

interface InterfaceWithNumberKey {
  [key: number]: any; 
}

type KeyType1 = keyof InterfaceWithStringKey; // string | number;
type KeyType2 = keyof InterfaceWithNumberKey; // number;
TypeScript