TypeScript 那些常见类型表达式关键字
前言#
除了定义一些基本类型,某些情况不能用基本类型确定,也就是要定义一个复杂类型的时候,TS的一些操作符能够帮助我们解决这一问题,下面会结合实例讲述 TS 中一些常见的高级操作符,也欢迎大家补充。
关键字#
extends#
语法:T extends K
这里的 extends不是类,接口继承的意思,而是起到限定与约束作用,意思是 判断 T 能否赋值给 K
有时候定义的泛型类型不想什么类型都可以,想继承某些类,可以通过 extends关键字添加泛型约束。
const useCopyKeys = <T extends U, U>(target: T, source: U) => { for (let key in source) { target[key] = (source as T)[key]; } return target;};
const obj = { a: 1, b: 2, c: 3, d: 4 };useCopyKeys(obj, { b: 10, c: 20 });
上面约束了泛型 T 必须包含 U , 如果泛型 T 中没有 U 字段那么就会出现错误,简单来说就是,T 可以是 U 的父类,但是不能是 U 的子类。
也可以用于条件判断
extends 条件分发
例如,实例化T extends U ? X : Y,会存在一个特性,当T的类型为A | B | C也就是联合类型时,会进行条件分发,被解析为(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y),
下面Exclude工具类型,U 在 T 中有的都排除
type Exclude<T, U> = T extends U ? never : T;
type E0= Exclude<string | number, number>; // string
上面列子会被分发为string extends number ? never : string| number extends number ? never : number,因此得到string。
in#
in关键字用来枚举类型:
type Keys = 'status' | 'code';type Response = { [p in Keys]: any;};
// 等同于:type Response = { status: any; code: any;};
注意的是:
如果使用 [ P in number ]生成以数字为索引的类型的话,他也可以是一个数组:
以 TypeScript 内置工具方法 Record方法为列子:
// Record 实现方法type Record<K extends keyof any, T> = { [P in K]: T;};
interface Customer { name: string; age: number;}
type Result = Record<number, Customer>; // type Result = { [x: number]: Customer; }
const varible: Result = [{ name: 'jac', age: 111 }]; // OK
[x: number]可以代表它的索引因此值为[{ name: 'jac', age: 111 }]是成功的,相当于 [ 0 : { name: 'jac', age: 111 }]省略了 0,
但是如果是字符串索引的话,就不能这样
type Result = Record<string, Customer>; // type Result = { [x: number]: Customer; }
const varible: Result = [{ name: 'jac', age: 111 }]; // Error
const varible: Result = { info: { name: 'jac', age: 111 } }; // OK
is#
语法:prop is Type
判断 prop 是否是 Type 类型
is 关键字一般用于函数返回值类型中,判断参数是否属性某一类型,并根据结果返回对应的布尔类型。
下面判断 isError 函数传入的参数 value 是否是 Error 类型
export const ErrorBox = ({ error }: { error: unknown }) => { if (error?.message) { return <Typography.Text type={"danger"}>{error?.message}</Typography.Text>; } return null;}
上面 ErrorBox 组件 根据 props 传递 error 对象, error 因为有可能是 null 也有可能有值,不能确定,所以定义 unkown 类型,想根据 error?.message判断是否有值,有的话就显示自己的错误信息组件。但是 error 提示 '对象的类型为 "unknown"。',但是由于函数嵌套 TypeScript 不能进行正确的类型判断,这时候可以使用 is 准确范围。
// 类型守卫,将 value 的类型缩小为 Errorconst isError = (value: any): value is Error => value?.message;
export const ErrorBox = ({ error }: { error: unknown }) => { if (isError(error)) { return <Typography.Text type={"danger"}>{error?.message}</Typography.Text>; } return null;}
也可以看这里 TypeScript 关键字。
infer#
infer推断的变量类型是根据能够匹配最接近 extends 左边能够接收的类型从而推断的类型,会尝试匹配最靠近的类型。
infer 占位符关键字出现的位置:通常出现在以下三个位置。
- infer 出现在 extends 条件语句后的函数类型的参数类型位置上。
- infer 出现在 extends 条件语句后的函数类型的返回值类型上。
- infer 会出现在类型的泛型具体化类型上。
注意:infer 只能出现在 extends 条件语句中。
infer P 中的 P 可以接收任何类型
举例:
type InferParamType<T> = T extends (param: infer P) => any ? P : T;
infer P就是在条件判断中申明的新的泛型,它会根据实际传入的泛型类型推断出该泛型的具体类型
上面工具类型 InferParmaType的作用是拿到函数类型的参数类型,下面我们来使用它:
// 工具类型 InferTypetype InferParamType<T> = T extends (param: infer P) => any ? P : T;
interface Customer { custname: string; buymoney: number;}
type FuncType = (param: Customer) => string;
type InferResultType = InferParamType<FuncType>;
最终 InferResultType接收到的类型为 Customer:
为什么呢?
当执行工具类型 InferParamType<FuncType>相当于:
// type FuncType = (param: Customer) => string;
type InferParamType<FuncType> = FuncType extends (param: infer P) => any ? P : FuncType;
意思是:FuncType类型是否满足 (param: infer P) => any 如果满足 则返回 infer 定义的变量 P,否者返回 FuncType,首先看返回类型 any满足 FuncType的返回类型 string,infer定义的变量类型 P它是可以接收任何类型也就是 infer 推导出的类型,param的类型为 Customer,P并接收到会被定义为 Customer,因此 FuncType类型能够满足约束条件,所以返回P也就是 Customer类型。
再举个不成立的例子:
这里从新定义FuncType的类型,增加一个 nums 参数:
// 工具类型 InferType// type InferParamType<T> = T extends (param: infer P) => any ? P : T;
type FuncType = (param: Customer,nums: number) => string;
type InferResultType = InferParamType<FuncType>;
此时 InferResultType得到的类型就是 传入的类型 FuncType:
因为多个参数函数类型,不满足比他少的参数函数类型,所以返回了 FuncType。
加强理解题(TS 面试题目):
实现一个LeftTrim字符串类型去左侧空格
type A = ' Hello World! ';type B = LeftTrim<A>;
答案:
使用 infer,以及递归。
type LeftTrim<T extends string> = T extends ` ${infer R}` ? LeftTrim<R> : T
infer推导出的变量类型为 Hello World! (Hello World! 是一个字面量类型并且没有空格的)来 R 保存,因为模板字符串中前面又一个空格了,第一次会满足条件,所以会递归将变量 R(Hello World! )再次给 LeftTrim,第二次进入 此时 T 的类型是Hello World! ,extends 后面的类型会变为 Hello World!这个有空格,此时 T 类型是没有空格了的,所以不满足约束条件,返回 T,因此得到的是:
那么去掉右边的空格就是:
type LeftTrim<T extends string> = T extends `${infer R} ` ? LeftTrim<R> : T
typeof#
typeof操作符可以用来判断数据的类 型是否是某个原始类型(number、string、boolean,...)和对象类型。
interface User { name: string; useId: number;}
const obj: User = { name: 'ssn', useId: 888000 }type TUser = typeof obj// type TUser = User
typeof 类型保护
function greet(person: string | string[]): string | string[] { if (typeof person === 'string') { return `Hello, ${person}!`; } else if (Array.isArray(person)) { return person.map(name => `Hello, ${name}!`); } throw new Error('Unable to greet');}greet('World'); // 'Hello, World!'greet(['Jane', 'Joe']); // ['Hello, Jane!', 'Hello, Joe!']
greet 内部会 typeof 判断传递的参数的类型,来做对应逻辑是输出字符串,还是字符串数组。
instance#
与typeof类似,但是作用方式不同,instanceof类型保护是通过构造函数来细化类型的一种方式。
instanceof 的右侧要求是一个构造函数。
class Song { constructor(public title: string, public duration: number) {}}
class Playlist { constructor(public name: string, public songs: Song[]) {}}
function getItemName(item: Song | Playlist) { if(item instanceof Song) { return item.title; } return item.name;}
const songName = getItemName(new Song('Wonderful Wonderful', 300000));console.log('Song name:', songName) // Song name: Wonderful Wonderful
const playlistName = getItemName(new Playlist('The Best Songs', [new Song('The Man', 300000)]));console.log('Playlist name:', playlistName); // Playlist name: The Best Songs
上面代码,getItemName 函数中使用 instanceof操作符来推断传入的 item 参数是 Song 类型还是 Playlist 类型执行对应的操作。
keyof#
语法: keyof T
该操作符可以用于获取 T 类型的所有已知公共属性键,其返回类型是联合类型。
interface User { name: string; useId: number;}type UserKeys = keyof User; // type UserKeys = 'name' | 'useId'
如果属性被定义为私有属性:
interface User { name: string; private useId: number;}type UserKeys = keyof User; // type UserKeys = 'useId'
在 TypeScript 规定中,如果说是 keyof any那么此时any包含的类型为 string | number | symbol。
索引访问操作符号 T[K]#
语法: T[K]
类似于 JS 中使用对象索引的方式,只不过js 中是返回对象属性的值,而在 TypeScript 中返回的是 T
对应属性 K 的类型。
interface User { name: string; useId: number;}
type UserNmaeType = User['name'] // string
最后#
如果对上述有不明白,或者描述有误,欢迎评论区指出~ 想试试自己学得怎么样了,可以做做这48道TS练习题,下面这篇文章里,也附有答案加解析。