概述
Javascript 中一切都是变量,Typescript 中也一样,类型也可以作为一个“变量”来进行一些操作,比如之前介绍的高级类型,就是在基础类型和复合类型的基础上扩展出的类型。
Typescript 中一些操作符可以对类型进行进一步的操作,包括:
- typeof:类型获取
- keyof:索引类型获取
- as:类型断言
- []:成员类型
- extends:条件类型
操作符
typeof:类型获取
Javascript 本身有typeof操作符,作用是获取变量类型名,返回值是字符串。Typescript 中的typeof可以对变量使用,也可以对类型使用。对变量使用时作用和 Javascript 一样,对类型使用时可以取得变量的类型。
示例:
let a = { name: 'a', value: 0 }
type A = typeof a
let b: A = { name: 'b', value: 1 }
上述示例中,A是通过typeof取得的类型,它通过变量来获取对应类型。
keyof:索引类型获取
keyof操作符接受一个对象类型,返回其成员名组成的字面量类型。
示例:
interface A {
name: string
value: number
}
type AMember = keyof A // 'name' | 'value'
上述示例中,类型AMember就是字面量类型:'name' | 'value'。
如果对象类型中声明了索引成员的话,keyof操作的就过是索引名的类型。示例:
interface A {
[index: number]: string
}
type AMember = keyof A // number
由于 Javascript 中通过索引获取对象成员时,如果索引名是数字,也会被转换成字符串。所以,在 Typescript 中如果索引名是字符串类型,那么keyof操作结果是'string' | 'number'。
应用:成员名约束
一个常见的应用是在泛型中,利用keyof来约束对象成员名。
示例:
function print<T, U extends keyof T>(target: T, name: U) {
let value = target[name]
console.log(value)
}
上述示例中,先通过keyof T获取类型T的成员名的可能值(字面量类型),再通过泛型约束(extends)表示类型变量U必须包含类型T的成员允许的类型。效果是,参数name必须是参数target的成员名之一。
as:类型断言
类型断言常被误解为类型转换,但是断言只是告诉编译器某个变量的是更具体或更宽泛的类型,并没有对变量进行改造而变成另外一个类型。这种情况常常发生在,编译器不知道某个变量的具体类型,只知道它是某个基类,而使用者知道变量的具体为某个派生类,可以明确的告诉编译器其类型。
Typescript 中使用关键词as或尖括号<>表示类型断言。示例:
let a = undefined as string | undefined
a = 'a'
let b = <{ value: number } | null>null
b = { value: 0 }
'a'是undefined类型变量,无法赋值为字符串,我们将其断言为string | undefined,表示这个类型既可以是字符串也可以是undefined。
一个常见的应用是在浏览器中当我们用document.getElementById方法获取元素对象时,方法的返回值类型是HTMLElement,但我们明确知道我们获取的元素时什么类型,那我们就可以使用类型断言让编译器知道具体的类型。示例:
let canvas = document.getElementById('canvas') as HTMLCanvasElement
[]:成员类型
类型可以使用操作符[],操作数是成员名,返回成员的类型。
示例:
interface A {
name: string
value: number
}
type AName = A['name'] // string
可以使用字面量联合类型来获取一个有成员类型组成的联合类型。示例:
type B = A['name' | 'value'] // string | number
type C = A[keyof A] // string | number
如果类型有索引,我们也可以获取其索引值类型。示例:
interface A {
[index: string]: boolean
}
type B = A[string] // boolean
注意,由于string类型的索引名能兼容number类型,所以上述示例的类型B可以改为:type B = A[number],但对于number类型的索引名,不能用string类型来获取索引值类型。
extends:条件类型
表达式<type> extends <target> ? <true-expression> : <false-expression>可以通过判断类型是否满足条件来决定返回类型。
示例:
interface A {
name: string
}
interface B extends A {
value: number
}
type C = B extends A ? string : number
类型C由类型B是否兼容类型A决定,如果兼容为string类型,否则为number类型。
这里的兼容的含义将在之后介绍。
这一特性常用在泛型中。示例:
type C<T> = T extends A ? string : number
type D = C<B> // string
infer
为了增强推断类型的能力,Typescript 增加了操作符infer,它的功能是“推测”这里有一个类型。它只能用在条件类型的真值表达式中。
示例:
type Flatten<T> = T extends Array<infer Item> ? Item : Type
这个泛型的作用是如果T类型是数组或数组的派生类,那么返回元素类型,否则返回类型本身,达到“类型平坦化”的目的。这里infer Item的作用相当于“创建了一个类型变量”,推测这里有个类型,供后面的真值表达式使用。
类型扩散
如果联合类型传入有条件类型表达式的泛型中,那么联合类型中的各类型会先拆分再组合。
示例:
type ToArray<T> = T extends any ? T[] : never
type StrArrOrNumArr = ToArray<string | number> // string[] | number[]
相当于string和number类型先从string | number中拆分,各自进入ToArray<T>,成为string[]和number[],在组合成string[] | number[]。
如果想避免这种行为,可以这样写:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never
type StrOrNumArr = ToArrayNonDist<string | number> // (string | number)[]