类型操作

Typescript 有着非常强大的类型系统,因为它允许你根据其他类型来表示一个新的类型。

keyof 类型运算符

keyof 运算符接受一个对象类型,并使用该对象类型的键生成字符串或数字类型的并集。

// 👉 以下类型P与'x'|'y'|'z'类型相同

type Point = { x: number; y: number; z: number };

type P = keyof Point;

如果类型的索引签名是字符串或数字,keyof 会返回这些类型:

type Arrayish = { [n:number]: unknown }

type A = keyof Arrayish // type A = number

type Mapish = {[ket:string]: boolean}

type M = keyof = string | number

这里类型 M=string|number,这是因为在 JavaScript 中,对象的键总是会被强制转换为字符串,所有 obj[0]总是和 obj[‘0’]的效果是相同的。

在 JavaScript 中访问对象属性的方式有两种,一种方式是 obj.name,另一种则是 obj['name']的方式,而当你在 TypeScript 中这样操作的时候,就会触发类型异常。

  • 解决方案一:在 tsconfig.json 中 compilerOptions 里配置如下代码,忽略掉此错误:

  • 解决方案二:在定义的 interface 里对其进行声明 ✨

  • 解决方案三:使用 keyof 进行判断

typeof 运算符

JavaScript 中已经存在一个 typeof 运算符,可以在表达式上下文中使用:

在 TypeScript 添加了一个运算符,使得我们可以在类型上下文中使用 typeof 来引用变量或者属性的类型。

这对基本类型并不是特别有用,但是当你结合其他类型运算符时,会有意想不到的便利,例如:

索引类型访问

通过索引类型我们可以访问另外一个类型中特定的属性类型。比方我们只需要用到 interface 下面某个属性的类型时,这很有用。

另外一种是使用任意类型进行索引,例如,使用 number 获取数组元素的类型,我们可以将它与 typeof 结合起来获取数组的元素类型。

条件类型

通过条件类型,我们可以通过不同的类型判断来决定最终输出的类型。

type Example1 = Dog extends Animal ? number : string; // number

这种操作形式类似于 JavaScript 中的三元表达式:condition ? trueExpression : falseExpression

上面的示例可能看起来并没有什么明显的效果,因为所有的的走向你都是知道的。但是当条件类型和泛型一起使用时,才能体现出它的强大之处。

该函数有三个重载函数,根据输入的类型做出不同的执行,在这种情况下,如果没有条件类型,我们就需要创建三个重载,相当麻烦,而通过条件类型则可以简化这一操作。

条件类型约束

通常条件类型可以缩小类型范围,就像类型保护一样进行缩小范围以提供更具体的类型,条件类型也可通过缩小范围来提供一个更精确的类型。例如限制类型范围。

约束为数组类型:

条件类型中进行推断

条件类型为我们提供了一种使用 infer 关键字从 true 分支中与之进行比较的类型进行推断的方法,例如:

在这里,我们使用 infer 关键字以声明方式引入一个名为 Item 的新泛型类型变量,而不是指定如何在 true 分支中检索元素类型 T。

例如,我们可以从函数类型中提取返回类型。

分配条件类型

当传入的类型参数为联合类型时,它们会被分配类型。

通常,分布性是所需的行为。要避免这种行为,可以用方括号包起 extends 关键字的两边。

映射类型

映射类型建立在索引签名的语法之上,索引签名用于声明没有提前声明的属性类型。例如:

映射类型是一种泛型类型,它使用 ProperKeys 的迭代键来创建类型。例如:

映射修饰符

在映射期间可以应用两个额外的修饰符 readonly 和?分别影响可变性和可选性。你可以通过前缀-、+来删除或添加这些修饰符,如果你不添加前缀,则默认为+

通过 as 键重映射

你可以利用模板字面量类型等功能从先前的属性名创建新的属性名称:

你也可以通过条件类型生成来过滤掉某一部分键。例如:

你可以映射人意联合类型,不仅仅是 string|number|symbol,还可以是任何类型的联合。

模板字面量类型

keyof

in

[] 运算符

方括号运算符([])用于取出对象的键值类型,比如 T[K]会返回对象 T 的属性 K 的类型。

方括号运算符的参数也可以是属性名的索引类型。

在上面示例中,Obj 的属性名是字符串的索引类型,所以可以写成 Obj[string],代表所有字符串属性名,返回的就是他们的类型 number。

这个语法对于数组也适用,可以使用 number 作为方括号的参数。

上面的示例中,array 是一个数组,它的类型实际上是属性名的数值索引,而 typeof array[number]typeof 运算符优先级高于方括号,所以返回的是所有数值键名的键值类型 string

extend...?: 条件运算符

条件运算符 extends...?: 可以根据当前类型是否符合某种条件,返回不同的类型。

如果T能够赋值给类型U,表达式的结果为类型X,否则结果为类型Y

上面示例中,1 是 number 的子类型,所以返回 true

当泛型的类型参数是一个联合类型时,那么条件运算符会展开这个类型参数,即 T<A|B> = T<A> | T<B>,所以 extends 对类型参数的每个部分都是分别计算的。

条件运算符还可以嵌套使用。

infer 关键字

infer关键字用来定义泛型里面推断出来的类型参数,而不是外部传入的类型参数。

它通常跟条件运算符一起使用,用在 extends 关键字后面的父类型之中。

上面示例中,infer Item表示Item这个参数是 TypeScript 自己推断出来的,不用显式传入,而Flatten<Type>则表示Type这个类型参数是外部传入的。Type extends Array<infer Item>则表示,如果参数Type是一个数组,那么就将该数组的成员类型推断为Item,即Item是从Type推断出来的。

一旦使用Infer Item定义了Item,后面的代码就可以直接调用Item了。下面是上例的泛型Flatten<Type>的用法。

如果不用infer定义类型参数,那么就要传入两个类型参数。

下面的例子使用infer,推断函数的参数类型和返回值类型。

上面示例中,如果T是函数,就返回这个函数的 Promise 版本,否则原样返回。infer A表示该函数的参数类型为Ainfer R表示该函数的返回值类型为R

如果不使用infer,就不得不把ReturnPromise<T>写成ReturnPromise<T, A, R>,这样就很麻烦,相当于开发者必须人肉推断编译器可以完成的工作。

下面是infer提取对象指定属性的例子。

上面示例中,infer提取了参数对象的属性a和属性b的类型。

下面是infer通过正则匹配提取类型参数的例子。

上面示例中,rest是从模板字符串提取的类型参数。

is 运算符

函数返回布尔值的时候,可以使用 is 运算符,限定返回值与参数之间的关系。

上面示例中,函数 isFish() 的返回值类型为 pet is Fish,表示如果参数 pet 类型为 Fish,则返回值为 true,否则返回 false

is 运算符总是用于描述函数的返回值类型,写法采用 parameterName is type 的形式,即左侧为当前函数的参数名,右侧为一种类型。它返回一个布尔值,表示左侧的参数是否与右侧的参数类型相同。

is 运算符可以用于类型保护。

上面示例中,函数isCat()的返回类型是a is Cat,它是一个布尔值。后面的if语句就用这个返回值进行判断,从而起到类型保护的作用,确保x是 Cat 类型,从而x.meow()不会报错(假定Cat类型拥有meow()方法)。

is运算符还有一种特殊用法,就是用在类(class)的内部,描述类的方法的返回值。

上面示例中,isStudent()方法的返回值类型,取决于该方法内部的this是否为Student对象。如果是的,就返回布尔值true,否则返回false

注意,this is T这种写法,只能用来描述方法的返回值类型,而不能用来描述属性的类型。

模板字符串

TypeScript 模板字符串最大的特点就是内部可以引用其他类型。

模板字符串可以引用的类型一共 7 种,分别是 stringnumberbigintbooleannullundefinedEnum。引用这 7 种以外的类型会报错。

模板字符串里面引用的类型,如果是一个联合类型,那么它返回的也是一个联合类型,即模板字符串可以展开联合类型。

如果模板字符串引用两个联合类型,它会交叉展开这两个类型。

satisfies 运算符

这有帮助吗?