TS高级类型篇
更新日期:
TypeScript 高级类型
交叉类型
使用符号 & 合并类型
1 | interface Product { |
联合类型
用符号竖线 | 分割每个类型。
1 | interface Fish { |
因为是多种类型的并集,所以使用的时候需要 进行类型保护和区分,以确保能正确访问
类型守卫
in
1
2
3
4
5
6
7function petShow(pet: Pet) {
if ('swim' in pet) {
pet.swim()
} else {
pet.fly()
}
}自定义类型保护
类型谓词
1
2
3
4
5
6
7
8
9
10function isFish(pet: Pet): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
function petShow(pet: Pet) {
if (isFish(pet)) {
pet.swim()
} else {
pet.fly()
}
}typeof
1
2
3
4
5
6
7
8
9function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}这些* typeof类型保护*只有两种形式能被识别: typeof v === “typename”和 typeof v !== “typename”, “typename”必须是 “number”, “string”, “boolean”或 “symbol”。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
instanceof
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
26
27
28
29
30
31
32
33interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 类型细化为'StringPadder'
}类型别名
给类型起个新名字
1
2
3
4
5
6
7
8
9
10
11type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n
}
else {
return n()
}
}索引类型
目的:编译器就能够检查使用了动态属性名的代码
先简单了解一下,泛型会详解
例如,一个常见的JavaScript模式是从对象中选取属性的子集。
1 | function pluck(o, names) { |
下面是如何在TypeScript里使用此函数,通过 索引类型查询和 索引访问操作符:
1 | function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] { |
编译器会检查 name是否真的是 Person的一个属性。 本例还引入了几个新的类型操作符。 首先是 keyof T, 索引类型查询操作符。 对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合。 例如:
1 | let personProps: keyof Person; // 'name' | 'age' |
keyof Person是完全可以与 ‘name’ | ‘age’互相替换的。 不同的是如果你添加了其它的属性到 Person,例如 address: string,那么 keyof Person会自动变为 ‘name’ | ‘age’ | ‘address’。 你可以在像 pluck函数这类上下文里使用 keyof,因为在使用之前你并不清楚可能出现的属性名。
第二个操作符是 T[K], 索引访问操作符。 在这里,类型语法反映了表达式语法。 这意味着 person[‘name’]具有类型 Person[‘name’] — 在我们的例子里则为 string类型。 然而,就像索引类型查询一样,你可以在普通的上下文里使用 T[K],这正是它的强大所在。 你只要确保类型变量 K extends keyof T就可以了。 例如下面 getProperty函数的例子:
1 | function getProperty<T, K extends keyof T>(o: T, name: K): T[K] { |
getProperty里的 o: T和 name: K,意味着 o[name]: T[K]。 当你返回 T[K]的结果,编译器会实例化键的真实类型,因此 getProperty的返回值类型会随着你需要的属性改变。
1 | let name: string = getProperty(person, 'name'); |
索引类型和字符串索引签名
keyof和 T[K]与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型,那么 keyof T会是 string。 并且 T[string]为索引签名的类型:
1 | interface Map<T> { |
映射类型
一个常见的任务是将一个已知的类型每个属性都变为可选的:
1 | interface PersonPartial { |
这在JavaScript里经常出现,TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。
1 | type Partial<T> = { |
下面的例子辅助理解一下 in 语法:
1 | type Keys = 'option1' | 'option2'; |
它的语法与索引签名的语法类型,内部使用了 for .. in。 具有三个部分:
- 类型变量 K,它会依次绑定到每个属性。
- 字符串字面量联合的 Keys,它包含了要迭代的属性名的集合。
- 属性的结果类型。
在这个简单的例子里, Keys是硬编码的的属性名列表并且属性类型永远是 boolean,因此这个映射类型等同于TypeScrpt内置了很多映射类型,后续泛型介绍1
2
3
4type Flags = {
option1: boolean;
option2: boolean;
}