高级类型
联合类型
联合类型表示一个值可以是几种类型之一。
type T = string | number
let val1:T = '' // ok
let val2:T = 1 // ok
type T1 = 'a' | 'b'
type T2 = 'a' | 'c'
type T3 = T1 | T2 // 'a' | 'b' | 'c'
如果一个值是联合类型,那么只能访问此联合类型的所有类型里共有的成员。
interface Bird {
fly: () => void;
layEggs: () => void;
}
interface Fish {
swim: () => void;
layEggs: () => void;
}
function getAnimal() : Bird | Fish{
...
}
getAnimal().layEggs(); // ok
getAnimal().fly(); // error
交叉类型
交叉类型是将多个类型合并为一个类型。
把多种类型叠加到一起成为一种类型,包含所属所有类型的特性。
interface IStyleShape {
width: string;
height: string;
}
interface IStyleColor {
background: string;
color: string;
}
let myDomStyle: IStyleShape & IStyleColor = {
width: "100px",
height: "100px",
background: "red",
color: "#666",
};
// myDomStyle 必须含有width、height、background、color四个属性
如果两个类型有相同的 key 并且不同的情况下,则交叉后该key的类型为never
interface IStyleShape {
width: string;
value: string
}
interface IStyleColor {
background: string;
value: number
}
type T = IStyleShape & IStyleColor
let val:T = {
width: '100px',
background: '#666',
value: (function () {
throw new Error()
})() // value 类型为never
}
联合类型进行交叉
type T1 = 'a' | 'b'
type T2 = 'a' | 'c'
type T3 = string | number
type crossType1 = T1 & T2 // 'a'
type crossType2 = T1 & T3 // 'a' | 'b'
类型关键字
ts 提供了多种用于类型的关键字,除了上面泛型中提到的 extends 外,主要还有
- 类型谓语 is
- typeof
- instanceof
- keyof
- T[K]
- in
- infer
这些关键字中类型谓语 is、typeof、instanceof 主要用于类型保护(或者叫类型收窄);
在上面联合类型的例子中,我们想通过 getAnimal() 调用 fly 或是 swim 时应该怎么做呢?可以通过类型断言的方式
let animal = getAnimal();
if((<Bird>animal).fly){
(<Bird>animal).fly();
}else{
(<Fish>animal).swim()
}
这种方式需要多次使用类型断言,比较麻烦,如果在if else 分支中可以明确确认 animal 的具体类型就方便很多了,这时就需要类型保护的表达式。
所谓 ts 的类型保护,其实就是通过一些表达式来确保在某个作用域中有确定的类型。
let myDiv = document.getElementById('app') // 类型为 HTMLElement | null
if(myDiv){
console.log(myDiv.scrollHeight)
}
// 例如上面的例子中 myDiv null 或者 dom元素类型,这时候在 if 条件表达式中做了一层类型收窄,以致在if的分支中将类型 HTMLElement | null 收窄为 HTMLElement
类型谓语 is
类型谓语是 parameterName is Type 这种形式, parameterName必须是来自于当前函数签名里的一个参数名。
例如上面联合类型的例子,我们可以写一个 isBird 函数
function isBird(animal: Bird | Fish) : animal is Bird{
return (<Bird>animal).fly !== undefined
}
这样就可以调用 fly 方法了
let animal = getAnimal()
if(isBird(animal)){
animal.fly() // ok
}else{
animal.swim() // ok
}
上面例子中通过类型谓语 is, if 分支中 ts 知道 animal 一定是 Bird 类型,else 分支中一定是 Fish 类型
上面例子类型谓语 is 就通过区分类型起到类型保护的作用
可能会觉得这个返回不是布尔值吗,如果将 isBird 函数返回值改为 布尔值,则起不到类型保护的作用
function isBird(animal: Bird | Fish) : boolean {
return (<Bird>animal).fly !== undefined
}
if(isBird(animal)){
animal.fly() // error 类型“Bird | Fish”上不存在属性“fly”。
}else{
animal.swim() // error 类型“Bird | Fish”上不存在属性“swim”
}
typeof
语法为 typeof v === "typename" 和 typeof v !== "typename"
typeof 也可用于类型保护
其中 typename 只能是 "number", "string", "boolean" 或 "symbol",虽然 typeof v 可以和其他字符串进行比较,但是 ts 不会把那些表达式识别为类型保护。
function calculate(val: string | number){
if(typeof val === 'string'){
return val.length
}else{
return val.toFixed()
}
}
instanceof
instanceof类型保护是通过构造函数来细化类型的一种方式。
instanceof 右侧只能是构造函数
class Bird {
fly() {
console.log("fly");
}
}
class Fish {
swim() {
console.log("swim");
}
}
function fun(animal: Bird | Fish){
if(animal instanceof Bird){
animal.fly()
}else{
animal.swim()
}
}
keyof
索引类型查询操作符
对于任何类型 T, keyof T的结果为 T上已知的公共属性名(注意是公共属性)的联合。
interface User{
name : string,
age : number
}
let userProps: keyof User // 相当于 let userProps: 'name' | 'age'
T[K]
索引访问操作符
类似获取对象索引值的方式,ts 类型中,T[K] 可以从 T 类型中获取 K 属性的类型
interface User{
name : string,
age : number
}
type nameString = User['name'] // 相当于 type nameString = string
let okVariable: nameString = '' // ok
let errorVariable: nameString = 121 // error
如果T[K]中的 K 不存在 T 的key中,则会报错
type t = User['height'] // error 类型“User”上不存在属性“height”
结合 T[K] 和上面的 keyof,可以发现,
T[keyof T]相当于 T 上的所有key类型组成的联合类型,例如
interface User {
name: string,
age: number,
unmarried: boolean,
height: number
}
type t = User[keyof User] // type t = string | number | boolean
in
TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。
映射类型中可以使用 in 将旧类型的每个属性以同种方式都转化为新类型。
type Props = 'name' | 'age';
type UserType = { [K in Props]: string };
// UserType 相当于
type UserType = {
name: string,
age: string,
};
语法为 K in Keys
Keys 为联合类型,是要迭代的属性名集合,K 是类型变量,代表每个属性。
例如我们可以将接口的每个类型变为可选
interface User{
name: string
age: number
}
type myPartial<T> = {
[K in keyof T]? : T[K]
}
// myPartial 相当于
type myPartial<T> = {
name?: string
age?: number
}
let myUser:myPartial<User> = {}
extends
前面泛型中说过,可用于类型约束
除了约束,T extends K 也可以判断 T 能否分配给 K ,做类型判断,格式类似三元运算符
type Allot<T> = T extends 'a' | 'b' ? T : null
type aType = Allot<'a'> // 'a' 'a' 类型可以分配给 'a' | 'b' 类型
type nullType = Allot<'c'> // null 'c' 类型不能分配给 'a' | 'b' 类型所以返回 null
如果 T extends K ,T 是联合类型,这时会依次判断该联合类型的所有子类型是否可以分配给 K (分发),依次判断完会将所有结果组合为新的联合类型
// 例如用上面例子的 Allot
type uniteType = Allot<'a' | 'b' | 'c'> // 相当于 'a' | 'b' | null
type toArray<T> = T extends any? T[] : never
type T1 = toArray<string | number> // type T1 = string[] | number[]
可以使用元组避开这种行为
type toArray<T> = [T] extends any? T[] : never
type T1 = toArray<string | number> // type T1 = (string | number)[]
infer
infer 声明会引入一个待推断的类型变量,
infer关键词只能在 extends 条件类型上使用,不能在其他地方使用。
type ArrayItemType<T> = T extends (infer P)[] ? P : null
type T0 = ArrayItemType<string[]> // string
type T1 = ArrayItemType<any[]> // any
type T2 = ArrayItemType<100> // null
type T3 = ArrayItemType<[string,number]> // string | number
例如上面例子中的 ArrayItemType<T> 中 T extends (infer P)[] 这里其实判断了传入的 类型变量 T 是不是数组,如果是,类型推断 P 会根据 T 推断出数组的组成类型并返回
一个多层嵌套条件类型的推断例子
type Unpacked<T> = T extends (infer U)[]
? U
: T extends (...args: any[]) => infer U
? U
: T extends Promise<infer U>
? U
: T;
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<() => string>; // string
type T3 = Unpacked<Promise<string>>; // string
type T4 = Unpacked<Promise<string>[]>; // Promise<string>
type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
相同的类型变量都处于协变位置,推断出的类型是联合类型
type Unite<T> = T extends { a: infer U; b: infer U } ? U : never;
type T1 = Unite<{ a: string; }>; // never
type T2 = Unite<{ a: string; b: string }>; // string
type T3 = Unite<{ a: string; b: number }>; // string | number
type T4 = Unite<{ a: string; b: number; c: boolean }>; // string | number
相同的类型变量都处于逆变位置,推断出的类型是交叉型
type Cross<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;
type T1 = Cross<{ a: (x: string) => void; b: (x: string) => void }>; // string
type T2 = Cross<{ a: (x: string) => void; b: (x: number) => void }>; // never(string & number 就是 never)
type T3 = Cross<{ a: (x: {a: string }) => void; b: (x: { b: number}) => void }>; // { a: string } & { b: number }
ts 预定义的工具类型
ts 内部有预定义了一些工具类型,常用的有:
Partial<T>
将T类型的所有属性都改为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
例如
interface User{
name: string
age: number
}
let people: Partial<User> = {} // ok
people.age = 19 // ok
Required<T>
将T类型的所有属性都改为必选
type Required<T> = {
[P in keyof T]-?: T[P];
};
例如
interface User{
name?: string
age?: number
}
let peopleOne: Required<User> = {
name: 'jack',
age: 18,
} // ok
let peopleTwo: Required<User> = {
name: 'mike',
} // error 缺少 age 属性
Readonly<T>
将T类型的所有属性都改为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
例如
interface User{
name: string
age: number
}
let people: Readonly<User> = {
name: 'jack',
age: 25
}
people.age = 19 // error, 不可修改
ReadonlyArray<T>
类型为 T[] ,且数组在初始化后不可修改
源码这个有点长,简单看下就行
interface ReadonlyArray<T> {
readonly length: number;
toString(): string;
toLocaleString(): string;
concat(...items: ConcatArray<T>[]): T[];
concat(...items: (T | ConcatArray<T>)[]): T[];
join(separator?: string): string;
slice(start?: number, end?: number): T[];
indexOf(searchElement: T, fromIndex?: number): number;
lastIndexOf(searchElement: T, fromIndex?: number): number;
every<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): this is readonly S[];
every(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): boolean;
some(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): boolean;
forEach(callbackfn: (value: T, index: number, array: readonly T[]) => void, thisArg?: any): void;
map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[];
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
filter(predicate: (value: T, index: number, array: readonly T[]) => unknown, thisArg?: any): T[];
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U, initialValue: U): U;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T): T;
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T, initialValue: T): T;
reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: readonly T[]) => U, initialValue: U): U;
readonly [n: number]: T;
}
例如
let arr : ReadonlyArray<string> = ['js', 'ts']
arr[0] = 'vue' // error 不能修改
arr.length = 5 // error 不能修改长度
arr.push("react") // 没有 push 方法
Pick<T,K>
从类型 T 提取部分 K 属性作为新的类型
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
例如
interface User{
name: string
age: number
gender: string
}
let peopleOne: Pick<User, 'name'> = {
name: 'jack',
}
people.age = 19 // error, 类型“Pick<User, "name">”上不存在属性“age”
let peopleTwo: Pick<User, 'name' | 'gender'> = {
name: 'jack',
gender: 'man'
}
Exclude<T, U>
从T中排除那些可赋给U的类型
type Exclude<T, U> = T extends U ? never : T;
例如
type strExclude = Exclude<'a'|'b'|'c', 'b'|'c'|'d'> // 'a'
其实上面的例子应该是返回 "a" | never,但是never 与其他类型联合会去掉 never
即
type T1 = "a" | never // 相当于 type T1 = "a"
Extract<T, U>
从T中提取那些可以赋值给U的类型
type Extract<T, U> = T extends U ? T : never;
例如
type strExtract = Extract<'a'|'b'|'c', 'b'|'c'|'d'> // 'b' | 'c'
Omit<T, K>
从 T 类型中去除 K 属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
上面的 keyof any 相当于 string | number | symbol,因为作为索引的类型只能是 string | number | symbol
例如
interface User{
name: string
age: number
gender: string
}
type omitExample = Omit<User, 'name' | 'age'>
let people: omitExample = {
gender: 'man',
}
Record<K, T>
构造具有类型 T 的一组属性 K 的类型
type Record<K extends keyof any, T> = {
[P in K]: T;
};
例如
type recordExample = Record<'name' | 'age' | 'gender', string>
let people:recordExample = {
name: 'jack',
age: '20',
gender: 'man',
}
借用这个工具,我们也可以方便的定义一个不确定属性类型的对象类型
type objType = Record<string, any>
// 相当于
type objType = {
[x: string]: any;
}
ReturnType<T>
获取函数类型的返回类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
例如
type funType = (name:string, age:number) => string
type InstanceTypeExample2 = ReturnType<funType> // string
Parameters<T>
返回函数类型的参数组成的元组
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
例如
type funType = (name:string, age:number) => boolean
type parametersExample = Parameters<funType> // [string, number]
NonNullable<T>
从 T 中排除null和undefined
type NonNullable<T> = T extends null | undefined ? never : T;
例如
type myType = string | number | undefined | null
type noNullMyType = NonNullable<myType> // string | number
InstanceType<T>
获取构造函数类型的返回类型
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
通过观察代码可以发现 T extends abstract new (...args: any) => any 这里T被约束为构造函数,为什么要用 abstract 抽象类关键字呢
因为当类型定义为抽象类,那么既可以赋值为抽象类,也可以赋值为普通类
class Animal {}
abstract class AnimalAbstract {}
const myAnimal1: typeof Animal = Animal // ok
const myAnimal2: typeof Animal = AnimalAbstract // error 无法将抽象构造函数类型分配给非抽象构造函数类型
const myAnimal3: typeof AnimalAbstract = Animal // ok
const myAnimal4: typeof AnimalAbstract = AnimalAbstract // ok
InstanceType例子
interface User {
new (name:string, age:number) : {name:string, age:number}
}
type InstanceTypeExample = InstanceType<User> // { name:string, age:number }
class User {
name: string | undefined
age: number | undefined
constructor (name: string, age: number){
this.name = name
this.age = age
}
}
type InstanceTypeExample = InstanceType<typeof User> // User
// 例如在基于vue3的element-plus框架中,定义模板引用的 ref
<template>
<el-form
ref="formRef"
>
...
</el-form>
</template>
<script lang="ts" setup>
import type { ElForm } from 'element-plus'
const formRef = ref<InstanceType<typeof ElForm>>()
</script>
ConstructorParameters<T>
返回构造函数类型的参数类型组成的元组
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
例如
interface User {
new (name:string, age:number) : {name:string, age:number}
}
type ConstructorParamExample = ConstructorParameters<User> // [string, number]