【Typescript小小册】类型操作

原创
2021/04/09 15:46
阅读数 126

概述

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[]

相当于stringnumber类型先从string | number中拆分,各自进入ToArray<T>,成为string[]number[],在组合成string[] | number[]

如果想避免这种行为,可以这样写:

type ToArrayNonDist<T> = [T] extends [any] ? T[] : never

type StrOrNumArr = ToArrayNonDist<string | number> // (string | number)[]
展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部