添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

高级类型

TypeScript为了保障语言的灵活性,而引入的一些语言特效。这些特性有助于开发者应对复杂,多变的开发场景。

交叉类型

交叉类型是将多个类型合并为一个类型。 新的类型拥有所有类型的特性。我们大多是在对象混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在 JavaScript 里发生这种情况的场合很多!) 下面是如何创建混入的一个简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
interface DogInterface {
run(): void
}
interface CatInterface {
jump(): void
}

// 交叉类型
let pet: DogInterface & CatInterface = {
run() {},
jump() {}
}

联合类型

变量的类型并不确定,可以为多个类型中的一个。

1
let a: number | string = 1

如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Dog implements DogInterface {
run() { }
eat() { }
}
class Cat implements CatInterface {
jump() { }
eat() { }
}
enum Master { Boy, Girl }
function getPet(master: Master) {
let pet = master === Master.Boy ? new Dog() : new Cat();
// 类型“Dog | Cat”上不存在属性“run”。类型“Cat”上不存在属性“run”。
// pet.run()
// 类型“Dog | Cat”上不存在属性“jump”。类型“Dog”上不存在属性“jump”。
// pet.jump()
// 类型保护
if (pet instanceof Dog){
pet.run()
}else{
pet.jump()
}
pet.eat()
return pet
}

这里的联合类型可能有点复杂:如果一个值的类型是 A | B ,我们能够确定的是它包含了 A B 中共有的成员。这个例子里, Dog 具有一个 run 方法,我们不能确定一个 Cat | Dog 类型的变量是否有 jump 方法。 如果变量在运行时是 Dog 类型,那么调用 pet.jump() 就出错了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.height * s.width;
}
}
console.log(area({kind: 'circle', radius: 1}))

上面的方法,如果遗漏了 circle 计算面试的实现逻辑,程序是不会报错的。如果想用TypeScript约束这种错误,进行错误提示可以使用下面的方法:

  • 设置函数返回值类型
  • 设置never类型,设置default
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    // 设置函数返回值类型 "strictNullChecks": true
    // 返回undefined会报错
    function area(s: Shape): number {
    switch (s.kind) {
    case "square":
    return s.size * s.size;
    case "rectangle":
    return s.height * s.width;
    }
    }

    // 设置default
    function area(s: Shape) {
    switch (s.kind) {
    case "square":
    return s.size * s.size;
    case "rectangle":
    return s.height * s.width;
    case 'circle':
    return Math.PI * s.radius ** 2
    default:
    // 检测s是不是never类型,不是never类型,说明前面的分支判断有遗漏,会报错
    return ((e: never) => {throw new Error(e)})(s)
    }
    }

    字面量类型

    有些时候不仅需要限定变量的类型,而且还需要限定变量的取值在某个范围之类。

    1
    2
    3
    // 字面量联合类型
    let b: 'a' | 'b' | 'c'
    let c: 1 | 2 | 3

    索引类型

    索引类型会使用 keyof T , 索引类型查询操作符。

    使用索引类型,编译器就能够检查使用了动态属性名的代码。 例如,一个常见的JavaScript模式是从对象中选取属性的子集。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let obj = {
    name: '11',
    age: 22
    }

    function getValues(obj: any, keys: string[]) {
    return keys.map(key => obj[key])
    }

    console.log(getValues(obj, ['sex']))

    在上面的代码中 obj 对象没有 sex 属性,TypeScript没有报错,如果想TypeScript进行相关报错提示,可以使用索引类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function getValues<T, K extends keyof T>(o: T, keys: K[]): T[K][] {
    return keys.map(n => o[n]);
    }

    interface Person {
    name: string;
    age: number;
    }
    let person: Person = {
    name: 'Jarid',
    age: 35
    };
    let strings: string[] = getValues(person, ['name']); // ok, string[]

    上面的代码使用 泛型变量 T 约束 obj对象 ,使用 泛型变量 K 约束 keys数组 ,并给 K 增加 类型约束 ,让它继承 obj 所有属性的 联合类型 。函数的返回值是一个数组,数组的元素的类型就是 obj对象 的属性 K 对应的类型。

    编译器会检查 name 是否真的是 obj的一个属性。 本例还引入了几个新的类型操作符。 首先是 keyof T , 索引类型查询操作符。 对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合。 例如:

    1
    
    
    
    
        
    
    let personProps: keyof Person; // 'name' | 'age'

    第二个操作符是 T[K], 索引访问操作符。

    映射类型

    一个常见的需求是将一个已知的类型每个属性都变为可选的:

    1
    2
    3
    4
    interface PersonPartial {
    name?: string;
    age?: number;
    }

    或者我们想要一个只读版本:

    1
    2
    3
    4
    interface PersonReadonly {
    readonly name: string;
    readonly age: number;
    }

    这在JavaScript里经常出现,TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型 。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。 例如,你可以令每个属性成为 readonly类型或可选的。 下面是一些例子:

    1
    2
    3
    4
    // 只读
    type Readonly<T> = {
    readonly [P in keyof T]: T[P];
    }
    1
    2
    3
    4
    // 可选
    type Partial<T> = {
    [P in keyof T]?: T[P];
    }

    像下面这样使用:

    1
    2
    type PersonPartial = Partial<Person>;
    type ReadonlyPerson = Readonly<Person>;

    下面来看看最简单的映射类型和它的组成部分:

    1
    2
    type Keys = 'option1' | 'option2';
    type Flags = { [K in Keys]: boolean };

    它的语法与索引签名的语法类型,内部使用了 for .. in 。 具有三个部分:

    类型变量 K ,它会依次绑定到每个属性。
    字符串字面量联合的 Keys ,它包含了要迭代的属性名的集合。
    属性的结果类型。
    在个简单的例子里, Keys 是硬编码的的属性名列表并且属性类型永远是 boolean ,因此这个映射类型等同于:

    1
    2
    3
    4
    type Flags = {
    option1: boolean;
    option2: boolean;
    }

    常见的映射类型