Typescript 类型系统
jcLee95的个人博客
邮箱 :291148484@163.com
本文由原博客地址:https://blog.csdn.net/qq_28550263/article/details/123173417
迁移至阿里云:https://developer.aliyun.com/article/
目 录
- 1.9.1 数字枚举
- 1.9.2 字符串枚举
- 1.9.3 异构枚举
- 1.9.4 计算成员和常量成员
- 1.9.5 联合枚举和枚举成员类型
- 1.9.6 运行时的枚举
- 1.9.7 编译时的枚举
- 1.9.8 环境枚举
- 1.9.9 对象与枚举
- 5.1.1 Partial
- 5.1.2 Required
- 5.1.3 Readonly
- 5.1.4 Record<K extends keyof any, T>
- 5.1.5 Pick<T, K extends keyof T>
- 5.1.6 Omit<Type, Keys>
- 5.1.7 Exclude<UnionType, ExcludedMembers>
- 5.1.8 Extract<Type, Union>
- 5.1.9 NonNullable
- 5.1.10 Parameters<T extends (…args: any) => any>
- 5.1.11 ConstructorParameters<Type extends abstract new (…args: any) => any>
- 5.1.12 ReturnType<T extends (…args: any) => any>
- 5.1.13 InstanceType<T extends abstract new (…args: any) => any>
- 5.1.14 ThisParameterType
- 5.1.15 OmitThisParameter
- 5.1.16 ThisType
1. 日常类型
1.1 string
string
表示字符串值类型,如"Hello, world"。
小写字母开头的string
在JavaScript系统中表示的是字符串类型,对应由大写字母表示的构造器(函数)String所构造。String的TypeScript接口如下:
interface String { /** 返回字符串的字符串表示形式。 */ toString(): string; /** * 返回指定索引处的字符。 * @param pos 所需字符的从零开始的索引。 */ charAt(pos: number): string; /** * 返回指定位置的字符的Unicode值。 * @param index 所需字符的从零开始的索引。 如果在指定的索引处没有字符,则返回 NaN。 */ charCodeAt(index: number): number; /** * 返回包含两个或更多字符串串联的字符串。 * @param strings 要追加到字符串末尾的字符串。 */ concat(...strings: string[]): string; /** * 返回子字符串第一次出现的位置。 * @param searchString 要在字符串中搜索的子字符串 * @param position 开始搜索String对象的索引。如果省略,则从字符串的开头开始搜索。 */ indexOf(searchString: string, position?: number): number; /** * 返回子字符串在字符串中的最后一个匹配项。 * @param searchString 要搜索的子字符串。 * @param position 开始搜索的索引。如果省略,则从字符串末尾开始搜索。 */ lastIndexOf(searchString: string, position?: number): number; /** * 确定两个字符串在当前区域设置中是否等效。 * @param that 要与目标字符串进行比较的字符串 */ localeCompare(that: string): number; /** * 用正则表达式匹配字符串,并返回包含搜索结果的数组。 * @param regexp 包含正则表达式模式和标志的变量名或字符串文字。 */ match(regexp: string | RegExp): RegExpMatchArray | null; /** * 使用正则表达式或搜索字符串替换字符串中的文本。 * @param searchValue 要搜索的字符串。 * @param replaceValue 一个字符串,包含在此字符串中每次成功匹配 searchValue 时要替换的文本。 */ replace(searchValue: string | RegExp, replaceValue: string): string; /** * 使用正则表达式或搜索字符串替换字符串中的文本。 * @param searchValue 要搜索的字符串。 * @param replacer 返回替换文本的函数。 */ replace(searchValue: string | RegExp, replacer: (substring: string, ...args: any[]) => string): string; /** * 在正则表达式搜索中查找第一个匹配的子字符串。 * @param regexp 正则表达式模式和适用的标志。 */ search(regexp: string | RegExp): number; /** * 返回字符串的一部分。 * @param start stringObj的指定部分的开头的索引。 * @param end stringObj指定部分结尾的索引。子字符串包括字符,但不包括end指示的字符。如果未指定该值,子字符串将继续到stringObj的末尾。 */ slice(start?: number, end?: number): string; /** * 使用指定的分隔符将字符串拆分成子字符串,并将它们作为数组返回。 * @param separator 标识用于分隔字符串的一个或多个字符的字符串。如果省略,则返回包含整个字符串的单元素数组。 * @param limit 用于限制数组中返回的元素数量的值。 */ split(separator: string | RegExp, limit?: number): string[]; /** * 返回string对象中指定位置的子字符串。 * @param start 从零开始的索引号,指示子字符串的开头。 * @param end 从零开始的索引号,指示子字符串的结尾。子字符串包括字符,但不包括end指示的字符。 * 如果省略end,则返回从原始字符串的开头到结尾的字符。 */ substring(start: number, end?: number): string; /** 将字符串中的所有字母字符转换为小写。 */ toLowerCase(): string; /** 考虑到宿主环境的当前区域设置,将所有字母字符转换为小写。 */ toLocaleLowerCase(locales?: string | string[]): string; /** 将字符串中的所有字母字符转换为大写。 */ toUpperCase(): string; /** 根据宿主环境的当前区域设置,返回一个字符串,其中所有字母字符都已转换为大写。 */ toLocaleUpperCase(locales?: string | string[]): string; /** 从字符串中移除前导空格和尾随空格以及行终止符。 */ trim(): string; /** 返回字符串对象的长度。 */ readonly length: number; // IE 扩展 /** * 获取从指定位置开始并具有指定长度的子字符串。 * @deprecated 浏览器兼容性的传统功能 * @param from 所需子字符串的起始位置。字符串中第一个字符的索引为零。 * @param length 返回的子字符串中包含的字符数。 */ substr(from: number, length?: number): string; /** 返回指定对象的原始值。 */ valueOf(): string; readonly [index: number]: string; } interface StringConstructor { new(value?: any): String; (value?: any): string; readonly prototype: String; fromCharCode(...codes: number[]): string; } /** * 允许操作和格式化文本字符串以及确定和定位字符串中的子字符串。 */ declare var String: StringConstructor;
1.2 number
适用于像42.
JavaScript 对整数没有特殊的运行时值,因此没有等价于int
或float
- 一切都只是number类型。
小写字母开头的number
在JavaScript系统中表示的是数字类型,对应由大写字母表示的构造器(函数)Number所构造。Number的TypeScript接口如下:
interface Number { /** * 返回对象的字符串表示形式。 * @param radix 指定用于将数值转换为字符串的基数。该值仅用于数字。 */ toString(radix?: number): string; /** * 返回一个以定点表示法表示数字的字符串。 * @param fractionDigits 小数点后的位数。必须在0 - 20的范围内,包括0和20。 */ toFixed(fractionDigits?: number): string; /** * 返回一个字符串,其中包含用指数表示的数字。 * @param fractionDigits 小数点后的位数。必须在0 - 20的范围内,包括0和20。 */ toExponential(fractionDigits?: number): string; /** * 返回一个字符串,该字符串包含用指定位数的指数或定点表示法表示的数字。 * @param precision 有效位数。必须在1 - 21的范围内,包括1和21。 */ toPrecision(precision?: number): string; /** 返回指定对象的原始值。 */ valueOf(): number; } interface NumberConstructor { new(value?: any): Number; (value?: any): number; readonly prototype: Number; /** JavaScript中可以表示的最大数字。大约等于 1.79E+308。 */ readonly MAX_VALUE: number; /** JavaScript中可以表示的最接近零的数字。大约等于 5.00E-324。 */ readonly MIN_VALUE: number; /** * 不是数字的值。 * 在相等比较中,NaN不等于任何值,包括它本身。若要测试某个值是否等效于 NaN,请使用 isNaN 函数。 */ readonly NaN: number; /** * 小于JavaScript中可以表示的最大负数的值。 * JavaScript 将负 _INFINITY 值显示为 -infinity。 */ readonly NEGATIVE_INFINITY: number; /** * 大于JavaScript中可以表示的最大数字的值。 * JavaScript将正_INFINITY值显示为无穷大。 */ readonly POSITIVE_INFINITY: number; } /** 表示任何类型的数字的对象。所有的JavaScript数字都是64位浮点数。 */ declare var Number: NumberConstructor;
1.3 boolean
只有两个值true
和false
构成的类型。
小写字母开头的boolean
在JavaScript系统中表示的是布尔类型,对应由大写字母表示的构造器(函数)Boolean所构造。Boolean的TypeScript接口如下:
interface Boolean { /** 返回指定对象的原始值。 */ valueOf(): boolean; } interface BooleanConstructor { new(value?: any): Boolean; <T>(value?: T): boolean; readonly prototype: Boolean; } declare var Boolean: BooleanConstructor;
1.4 数组
要指定类似数组的类型[1, 2, 3],可以使用语法number[];此语法适用于任何类型(例如string[],字符串数组等)。
小写字母开头的array
在JavaScript系统中表示的是数组类型,对应由大写字母表示的构造器(函数)Array所构造。Array的TypeScript接口如下:
interface Array<T> { /** * Gets 或者 sets 数组的长度。 这比数组中的最高索引高一个数字。 */ length: number; /** * 返回数组的字符串表示形式。 */ toString(): string; /** * 返回数组的字符串表示形式。 使用它们的toLocaleString方法将元素转换为string。 */ toLocaleString(): string; /** * 从数组中移除最后一个元素并返回它。 * 如果数组为空,则返回undefined,并且不修改数组。 */ pop(): T | undefined; /** * 向数组末尾追加新元素,并返回数组的新长度。 * @param items 要添加到数组的新元素。 */ push(...items: T[]): number; /** * 组合两个或多个数组。 * 此方法返回一个新数组,而不修改任何现有数组。 * @param items 要添加到数组末尾的其他数组和/或项目。 */ concat(...items: ConcatArray<T>[]): T[]; /** * 组合两个或多个数组。 * 此方法返回一个新数组,而不修改任何现有数组。 * @param items 要添加到数组末尾的其他数组和/或项目。 */ concat(...items: (T | ConcatArray<T>)[]): T[]; /** * 将数组中的所有元素添加到一个字符串中,用指定的分隔符字符串分隔。 * @param separator 用于将数组中的一个元素与结果字符串中的下一个元素分隔开的字符串。 如果省略,数组元素用逗号分隔。 */ join(separator?: string): string; /** * 就地反转数组中的元素。 * 此方法使数组变异,并返回对同一数组的引用。 */ reverse(): T[]; /** * 从数组中移除第一个元素并返回它。 * 如果数组为空,则返回undefined,并且不修改数组。 */ shift(): T | undefined; /** * 返回数组的一部分的副本。 * 对于start和end,可以使用负索引来指示从数组末尾的偏移量。 * 例如,-2表示数组的倒数第二个元素。 * @param start 数组指定部分的开始索引。 * 如果start undefined ,则切片从索引0开始。 * @param end 数组指定部分的结束索引。这不包括索引“end”处的元素。 * 如果end undefined ,则切片扩展到数组的末尾。 */ slice(start?: number, end?: number): T[]; /** * 就地排序数组。 * 此方法使数组变异,并返回对同一数组的引用。 * @param compareFn 用于确定元素顺序的函数。 如果第一个参数小于第二个参数,它将返回一个负值,如果两个参数相等,则返回零,否则返回正值。如果省略,元素将按ASCII字符升序排序。 * ```ts * [11,2,22,1].sort((a, b) => a - b) * ``` */ sort(compareFn?: (a: T, b: T) => number): this; /** * 从数组中移除元素,如有必要,在它们的位置插入新元素,返回已删除的元素。 * @param start 数组中从零开始移除元素的位置。 * @param deleteCount 要移除的元素数量。 * @returns 包含已删除元素的数组。 */ splice(start: number, deleteCount?: number): T[]; /** * 从数组中移除元素,如有必要,在它们的位置插入新元素,返回已删除的元素。 * @param start 数组中从零开始移除元素的位置。 * @param deleteCount 要移除的元素数量。 * @param items 要插入到数组中代替已删除元素的元素。 * @returns 包含已删除元素的数组。 */ splice(start: number, deleteCount: number, ...items: T[]): T[]; /** * 在数组的开头插入新元素,并返回数组的新长度。 * @param items 要在数组开头插入的元素。 */ unshift(...items: T[]): number; /** * 返回数组中某个值第一次出现的索引,如果不存在,则返回-1。 * @param searchElement 要在数组中定位的值。 * @param fromIndex 开始搜索的数组索引。 如果省略fromIndex,则从索引0开始搜索。 */ indexOf(searchElement: T, fromIndex?: number): number; /** * 返回指定值在数组中最后一次出现的索引,如果不存在,则返回-1。 * @param searchElement 要在数组中定位的值。 * @param fromIndex 开始向后搜索的数组索引。如果省略 fromIndex,则从数组中的最后一个索引开始搜索。 */ lastIndexOf(searchElement: T, fromIndex?: number): number; /** * 确定数组的所有成员是否满足指定的测试。 * @param predicate 最多接受三个参数的函数。 * every 方法为数组中的每个元素调用谓词函数,直到谓词返回一个可强制为布尔值 false 的值,或者直到数组结束。 * @param thisArg this 关键字可以在谓词函数中引用的对象。 * 如果省略 thisArg,则使用 undefined 作为 this 值。 */ every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[]; /** * 确定数组的所有成员是否满足指定的测试。 * @param predicate 最多接受三个参数的函数。 every方法为数组中的每个元素调用谓词函数, * 直到谓词返回一个可强制为布尔值 false 的值,或者直到数组结束。 * @param thisArg this关键字可以在谓词函数中引用的对象。 * 如果省略 thisArg,则使用 undefined 作为 this 值。 */ every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean; /** * 确定指定的回调函数是否为数组中的任何元素返回true。 * @param predicate 最多接受三个参数的函数。 some方法为数组中的每个元素调用谓词函数, * 直到谓词返回一个可强制为布尔值true的值,或者直到数组结束。 * @param thisArg this关键字可以在谓词函数中引用的对象。 * 如果省略 thisArg,则使用 undefined 作为 this 值。 */ some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean; /** * 对数组中的每个元素执行指定的操作。 * @param callbackfn 最多接受三个参数的函数。 forEach为数组中的每个元素调用一次callbackfn函数。 * @param thisArg this 关键字可以在 callbackfn函数中引用的对象。 如果省略 thisArg,则使用 undefined 作为 this 值。 */ forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; /** * 对数组的每个元素调用定义的回调函数,并返回包含结果的数组。 * @param callbackfn 最多接受三个参数的函数。 map方法为数组中的每个元素调用一次callbackfn函数。 * @param thisArg this 关键字可以在 callbackfn函数中引用的对象。 如果省略 thisArg,则使用 undefined 作为 this 值。 */ map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; /** * 返回满足回调函数中指定条件的数组元素。 * @param predicate 最多接受三个参数的函数。 filter方法为数组中的每个元素调用一次谓词函数。 * @param thisArg this关键字可以在谓词函数中引用的对象。 如果省略 thisArg,则使用 undefined 作为 this 值。 */ filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[]; /** * 返回满足回调函数中指定条件的数组元素。 * @param predicate 最多接受三个参数的函数。 filter方法为数组中的每个元素调用一次谓词函数。 * @param thisArg this关键字可以在谓词函数中引用的对象。 如果省略 thisArg,则使用 undefined 作为 this 值。 */ filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[]; /** * 为数组中的所有元素调用指定的回调函数。 回调函数的返回值是累加的结果,并在下次调用回调函数时作为参数提供。 * @param callbackfn 最多接受四个参数的函数。reduce方法为数组中的每个元素调用一次callbackfn函数。 * @param initialValue 如果指定了initialValue,它将用作开始累加的初始值。对callbackfn函数的第一次调用将此值作为参数而不是数组值提供。 */ reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; /** * 为数组中的所有元素调用指定的回调函数。 回调函数的返回值是累加的结果,并在下次调用回调函数时作为参数提供。 * @param callbackfn 最多接受四个参数的函数。reduce方法为数组中的每个元素调用一次callbackfn函数。 * @param initialValue 如果指定了initialValue,它将用作开始累加的初始值。对callbackfn函数的第一次调用将此值作为参数而不是数组值提供。 */ reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; /** * 按降序为数组中的所有元素调用指定的回调函数。 回调函数的返回值是累加的结果,并在下次调用回调函数时作为参数提供。 * @param callbackfn 最多接受四个参数的函数。reduceRight方法为数组中的每个元素调用一次callbackfn函数。 * @param initialValue 如果指定了initialValue,它将用作开始累加的初始值。对callbackfn函数的第一次调用将此值作为参数而不是数组值提供。 */ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; /** * 按降序为数组中的所有元素调用指定的回调函数。 回调函数的返回值是累加的结果,并在下次调用回调函数时作为参数提供。 * @param callbackfn 最多接受四个参数的函数。reduceRight方法为数组中的每个元素调用一次callbackfn函数。 * @param initialValue 如果指定了initialValue,它将用作开始累加的初始值。对callbackfn函数的第一次调用将此值作为参数而不是数组值提供。 */ reduceRight<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; [n: number]: T; } interface ArrayConstructor { new(arrayLength?: number): any[]; new <T>(arrayLength: number): T[]; new <T>(...items: T[]): T[]; (arrayLength?: number): any[]; <T>(arrayLength: number): T[]; <T>(...items: T[]): T[]; isArray(arg: any): arg is any[]; readonly prototype: any[]; } declare var Array: ArrayConstructor;
1.5 对象类型
这指的对象类型是任何带有属性的 JavaScript 值,几乎是所有属性!要定义对象类型,我们只需列出其属性及其类型。要定义对象类型,我们只需列出其属性及其类型。
例如函数:
// The parameter's type annotation is an object type function printCoord(pt: { x: number; y: number }) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } printCoord({ x: 3, y: 7 });
在这里,我们使用具有两个属性的类型来注释参数 -x和y- 这两个属性都是 type number。您可以使用,或;分隔属性,最后一个分隔符是可选的。
每个属性的类型部分也是可选的。如果不指定类型,则假定为any类型。
可选属性
对象类型还可以指定它们的部分或全部属性是可选的。为此,请在属性名称后添加一个?
号:
function printName(obj: { first: string; last?: string }) { // ... } // Both OK printName({ first: "Bob" }); printName({ first: "Alice", last: "Alisson" });
1.6 any 类型
any是一个特殊的类型,当你不希望某个特定的值导致类型检查错误时,你可以使用它。any当你不想写出一个长类型只是为了让 TypeScript 相信特定的代码行是可以的时该类型很有用。
1.7 联合类型
TypeScript 的类型系统允许您使用各种运算符从现有类型中构建新类型。联合类型是由两种或多种其他类型组成的类型,表示可能是这些类型中的任何一种的值。我们将这些类型中的每一种都称为联合的成员。如:
function printId(id: number | string) { console.log("Your ID is: " + id); }
1.8 类型断言
有时你会得到关于 TypeScript 无法知道的值类型的信息。
例如,如果您正在使用document.getElementById,TypeScript 只知道这将返回某种,HTMLElement但您可能知道您的页面将始终具有HTMLCanvasElement具有给定 ID 的 。
在这种情况下,您可以使用类型断言来指定更具体的类型:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
与类型注释一样,类型断言被编译器删除,不会影响代码的运行时行为。
您还可以使用尖括号语法(除非代码在.tsx文件中),它是等效的:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
因为类型断言在编译时被删除,所以没有与类型断言关联的运行时检查。null如果类型断言错误,则不会出现异常或生成。
TypeScript 只允许类型断言转换为更具体或更不具体的类型版本。此规则可防止“不可能”的强制,例如:
const x = "hello" as number;
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
有时,此规则可能过于保守,并且不允许可能有效的更复杂的强制转换。如果发生这种情况,您可以使用两个断言,首先是any(或unknown,我们稍后会介绍),然后是所需的类型:
const a = (expr as any) as T;
1.9 枚举
枚举是 TypeScript 添加到 JavaScript 的一项功能,它不是 JavaScript 的类型级扩展。枚举允许描述一个值,该值可能是一组可能的命名常量之一。与大多数 TypeScript 功能不同,这不是对 JavaScript 的类型级添加,而是添加到语言和运行时的东西。正因为如此,这是一个你应该知道存在的功能,但除非你确定,否则可能会推迟使用。
1.9.1 数字枚举
我们将首先从数字枚举开始,如果您来自其他语言,可能会更熟悉。可以使用enum关键字定义枚举。
enum Direction { Up = 1, Down, Left, Right, }
上面,我们有一个Up用 初始化的数字枚举1。从那时起,以下所有成员都会自动递增。换句话说,Direction.Up有值1,Down有2,Left有3,Right有4。
如果我们愿意,我们可以完全不使用初始化器:
enum Direction { Up, Down, Left, Right, }
使用枚举很简单:只需将任何成员作为枚举本身的属性访问,并使用枚举的名称声明类型:
enum UserResponse { No = 0, Yes = 1, } function respond(recipient: string, message: UserResponse): void { // ... } respond("Princess Caroline", UserResponse.Yes);
没有初始化器的枚举要么需要放在第一位,要么必须在使用数字常量或其他常量枚举成员初始化的数字枚举之后。换句话说,以下是不允许的:
enum E { A = getSomeValue(), B, }
Enum member must have initializer.
1.9.2 字符串枚举
字符串枚举是一个类似的概念,但有一些细微的运行时差异,如下所述。在字符串枚举中,每个成员都必须使用字符串文字或另一个字符串枚举成员进行常量初始化。
enum Direction { Up = "UP", Down = "DOWN", Left = "LEFT", Right = "RIGHT", }
虽然字符串枚举没有自动递增的行为,但字符串枚举的好处是它们可以很好地“序列化”。换句话说,如果您正在调试并且必须读取数字枚举的运行时值,那么该值通常是不透明的——它本身并不能传达任何有用的含义(尽管反向映射通常会有所帮助)。字符串枚举允许您在代码运行时提供有意义且可读的值,而与枚举成员本身的名称无关。
1.9.3 异构枚举
从技术上讲,枚举可以与字符串和数字成员混合,但不清楚你为什么要这样做:
enum BooleanLikeHeterogeneousEnum { No = 0, Yes = "YES", }
除非您真的想以一种巧妙的方式利用 JavaScript 的运行时行为,否则建议您不要这样做。
1.9.4 计算成员和常量成员
每个枚举成员都有一个与之关联的值,可以是常量或计算值。在以下情况下,枚举成员被认为是常量:
- 它是枚举中的第一个成员,并且没有初始化程序,在这种情况下,它被分配了值0:
// E.X is constant: enum E { X, }
- 它没有初始化程序,并且前面的枚举成员是一个数字常量。在这种情况下,当前枚举成员的值将是前一个枚举成员的值加一。
// All enum members in 'E1' and 'E2' are constant. enum E1 { X, Y, Z, } enum E2 { A = 1, B, C, }
- 枚举成员使用常量枚举表达式进行初始化。常量枚举表达式是可以在编译时完全评估的 TypeScript 表达式的子集。一个表达式是一个常量枚举表达式,如果它是:
- 文字枚举表达式(基本上是字符串文字或数字文字)
- 对先前定义的常量枚举成员的引用(可以源自不同的枚举)
- 带括号的常量枚举表达式
- 应用于常量枚举表达式的+, -,一元运算符之一~
- +, -, *, /, %, <<, >>, >>>, &, |,^以常量枚举表达式作为操作数的二元运算符
- 将常量枚举表达式计算为NaNor是编译时错误Infinity。
在所有其他情况下,枚举成员被认为是计算的。
enum FileAccess { // constant members None, Read = 1 << 1, Write = 1 << 2, ReadWrite = Read | Write, // computed member G = "123".length, }
1.9.5 联合枚举和枚举成员类型
有一个特殊的未计算的常量枚举成员子集:文字枚举成员。文字枚举成员是没有初始化值的常量枚举成员,或者具有初始化为的值
- 任何字符串文字(例如"foo", "bar, “baz”)
- 任何数字文字(例如1, 100)
- 应用于任何数字文字的一元减号(例如-1, -100)
当枚举中的所有成员都具有文字枚举值时,一些特殊的语义就会发挥作用。
首先是枚举成员也变成了类型!例如,我们可以说某些成员只能具有枚举成员的值:
enum ShapeKind { Circle, Square, } interface Circle { kind: ShapeKind.Circle; radius: number; } interface Square { kind: ShapeKind.Square; sideLength: number; } let c: Circle = { kind: ShapeKind.Square, Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'. radius: 100, };
另一个变化是枚举类型本身有效地成为每个枚举成员的联合。使用联合枚举,类型系统能够利用它知道枚举本身中存在的确切值集的事实。正因为如此,TypeScript 可以捕获我们可能会错误地比较值的错误。例如:
enum E { Foo, Bar, } function f(x: E) { if (x !== E.Foo || x !== E.Bar) { // } }
This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
在那个例子中,我们首先检查是否xis not E.Foo。如果检查成功,那么我们的||will 短路,‘if’ 的主体将运行。但是,如果检查不成功,则只能是x,所以E.Foo看它是否等于 没有意义E.Bar。
1.9.6 运行时的枚举
枚举是运行时存在的真实对象。例如,以下枚举
enum E { X, Y, Z, }
实际上可以传递给函数
enum E { X, Y, Z, } function f(obj: { X: number }) { return obj.X; } // Works, since 'E' has a property named 'X' which is a number. f(E);
1.9.7 编译时的枚举
尽管枚举是运行时存在的真实对象,但keyof关键字的工作方式与您对典型对象的预期不同。相反,用于keyof typeof获取将所有 Enum 键表示为字符串的类型。
enum LogLevel { ERROR, WARN, INFO, DEBUG, } /** * This is equivalent to: * type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'; */ type LogLevelStrings = keyof typeof LogLevel; function printImportant(key: LogLevelStrings, message: string) { const num = LogLevel[key]; if (num <= LogLevel.WARN) { console.log("Log level key is:", key); console.log("Log level value is:", num); console.log("Log level message is:", message); } } printImportant("ERROR", "This is a message");
反向映射
除了为成员创建具有属性名称的对象外,数字枚举成员还获得从枚举值到枚举名称的反向映射。例如,在此示例中:
enum Enum { A, } let a = Enum.A; let nameOfA = Enum[a]; // "A"
TypeScript 将其编译为以下 JavaScript:
"use strict"; var Enum; (function (Enum) { Enum[Enum["A"] = 0] = "A"; })(Enum || (Enum = {})); let a = Enum.A; let nameOfA = Enum[a]; // "A"
在此生成的代码中,枚举被编译成一个对象,该对象存储正向 ( name-> value) 和反向 ( value-> name) 映射。对其他枚举成员的引用始终作为属性访问发出,并且从不内联。
请记住,字符串枚举成员根本不会生成反向映射。
const
枚举
在大多数情况下,枚举是一个完全有效的解决方案。然而,有时要求更严格。为了避免在访问枚举值时支付额外生成的代码和额外的间接成本,可以使用const枚举。常量枚举是使用枚举上的const修饰符定义的:
const enum Enum { A = 1, B = A * 2, }
常量枚举只能使用常量枚举表达式,并且与常规枚举不同,它们在编译期间会被完全删除。常量枚举成员在使用站点内联。这是可能的,因为 const 枚举不能有计算成员。
const enum Direction { Up, Down, Left, Right, } let directions = [ Direction.Up, Direction.Down, Direction.Left, Direction.Right, ];
在生成的代码中会变成:
"use strict"; let directions = [ 0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */, ];
常量枚举陷阱
内联枚举值一开始很简单,但会带来微妙的影响。这些陷阱仅与环境const 枚举(基本上是.d.ts文件中的 const 枚举)和在项目之间共享它们有关,但如果您正在发布或使用.d.ts文件,这些陷阱可能适用于您,因为tsc --declaration将.ts文件转换为.d.ts文件。
isolatedModules由于文档中列出的原因,该模式从根本上与环境常量枚举不兼容。这意味着如果您发布环境常量枚举,下游消费者将无法同时使用isolatedModules这些枚举值。
您可以在编译时轻松地从依赖项的版本 A 中内联值,并在运行时导入版本 B。版本 A 和 B 的枚举可以有不同的值,如果你不是很小心,会导致令人惊讶的错误if,比如使用错误的语句分支。这些错误特别有害,因为通常在构建项目的同时运行自动化测试,具有相同的依赖版本,完全忽略了这些错误。
importsNotUsedAsValues: "preserve"不会忽略用作值的 const 枚举的导入,但环境 const 枚举不保证运行时.js文件存在。无法解析的导入会在运行时导致错误。目前,明确省略导入的常用方法(仅类型导入)不允许 const enum values。
以下是避免这些陷阱的两种方法:
A. 根本不要使用 const 枚举。您可以在 linter 的帮助下轻松禁止 const 枚举。显然,这避免了 const 枚举的任何问题,但会阻止您的项目内联自己的枚举。与其他项目的内联枚举不同,内联项目自己的枚举没有问题,并且会影响性能。B. 不要发布环境常量枚举,在preserveConstEnums. 这是TypeScript 项目本身内部采用的方法。 preserveConstEnums为 const 枚举发出与普通枚举相同的 JavaScript。然后,您可以在构建步骤中安全地从文件中剥离const修饰符。.d.ts
这样下游消费者不会从您的项目中内联枚举,避免上述陷阱,但项目仍然可以内联自己的枚举,这与完全禁止 const 枚举不同。
1.9.8 环境枚举
环境枚举用于描述已经存在的枚举类型的形状。
declare enum Enum { A = 1, B, C = 2, }
环境枚举和非环境枚举之间的一个重要区别是,在常规枚举中,如果之前的枚举成员被认为是常量,那么没有初始化器的成员将被认为是常量。相比之下,没有初始值设定项的环境(和非常量)枚举成员始终被视为已计算。
1.9.9 对象与枚举
as const
在现代 TypeScript 中,当一个对象足够时,您可能不需要枚举:
const enum EDirection { Up, Down, Left, Right, } const ODirection = { Up: 0, Down: 1, Left: 2, Right: 3, } as const; // EDirection.Up; (enum member) EDirection.Up = 0 // ODirection.Up; // (property) Up: 0 // Using the enum as a parameter function walk(dir: EDirection) {} // It requires an extra line to pull out the values type Direction = typeof ODirection[keyof typeof ODirection]; function run(dir: Direction) {} walk(EDirection.Left); run(ODirection.Right);
支持这种格式而不是 TypeScript 的最大论据enum是它使您的代码库与 JavaScript 的状态保持一致,并且当/如果将枚举添加到 JavaScript 中时,您可以转向其他语法。
2. 使用type
关键字定义 类型别名
我们一直通过直接在类型注释中编写对象类型和联合类型来使用它们。这很方便,但通常希望多次使用同一个类型并用一个名称引用它。类型别名就是这样 -任何类型的名称。类型别名的语法是:
type Point = { x: number; y: number; }; // Exactly the same as the earlier example function printCoord(pt: Point) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } printCoord({ x: 100, y: 100 });
实际上,您可以使用类型别名来为任何类型命名,而不仅仅是对象类型。例如,类型别名可以命名联合类型:
type ID = number | string;
请注意,别名只是别名 - 您不能使用类型别名来创建相同类型的不同/不同“版本”。当您使用别名时,就好像您已经编写了别名类型。换句话说,这段代码可能看起来非法,但根据 TypeScript 是可以的,因为这两种类型都是同一类型的别名:
type UserInputSanitizedString = string; function sanitizeInput(str: string): UserInputSanitizedString { return sanitize(str); } // Create a sanitized input let userInput = sanitizeInput(getInput()); // Can still be re-assigned with a string though userInput = "new input";
3. 使用 interface 关键字定义 接口
3.1 接口
接口声明是命名对象类型的另一种方式:
interface Point { x: number; y: number; } function printCoord(pt: Point) { console.log("The coordinate's x value is " + pt.x); console.log("The coordinate's y value is " + pt.y); } printCoord({ x: 100, y: 100 });
就像我们在上面使用类型别名时一样,该示例就像我们使用匿名对象类型一样工作。TypeScript 只关心我们传递给的值的结构printCoord——它只关心它是否具有预期的属性。只关心类型的结构和功能是我们称 TypeScript 为结构类型类型系统的原因。
3.2 type定义类型 与 interface 定义接口的区别
类型别名和接口非常相似,在很多情况下您可以在它们之间自由选择。几乎所有的特性interface都可以在 中使用type,主要区别在于不能重新打开类型来添加新属性,而接口总是可扩展的。在大多数情况下,您可以根据个人喜好进行选择,TypeScript 会告诉您是否需要其他类型的声明。如果您想要启发式方法,请使用interface直到您需要使用type。
3.2.1 扩展
3.2.1.1 扩展接口
interface Animal { name: string } interface Bear extends Animal { honey: boolean } const bear = getBear() bear.name bear.honey
3.2.1.2 (通过交叉点) 扩展类型
type Animal = { name: string } type Bear = Animal & { honey: boolean } const bear = getBear(); bear.name; bear.honey;
3.2.2 修改
3.2.2.1 向现有界面添加新字段
interface Window { title: string } interface Window { ts: TypeScriptAPI } const src = 'const a = "Hello World"'; window.ts.transpileModule(src, {});
3.2.2.2 类型创建后无法更改
type Window = { title: string } type Window = { ts: TypeScriptAPI } // Error: Duplicate identifier 'Window'.
3.2.3 其它
- 类型别名可能不参与声明合并,但接口可以。
- 接口只能用于声明对象的形状,不能重命名原语。
- 接口名称将始终以其原始形式出现在错误消息中,但仅在按名称使用时才出现。
4. 从类型创建类型
4.1 泛型:将类型视作参数
能够创建一个可以在多种类型而不是单一类型上工作的组件。这允许用户使用这些组件并使用他们自己的类型。
function identity<Type>(arg: Type): Type { return arg; }
这里的 Type
是某一个类型,可以视作一个类型参数传入函数。一个更实际的例子,数组的构造函数是Array
函数,当我们表示的是一个 字符串数组时,接口对应为Array<T>
,也就是,小括号用来传入普通参数,尖括号用来传入类型参数。再如Map
对象,也可以用这种方式来限制键、值的类型,如Map<string, any>
则限制了键必须为string
类型。
更多关于泛型用法请参考官方文档:
https://www.typescriptlang.org/docs/handbook/2/generics.html
4.2 keyof 类型运算符
运算符采用keyof对象类型并生成其键的字符串或数字文字联合。以下类型 P 与“x” | “是”:
type Point = { x: number; y: number }; type P = keyof Point;
type P = keyof Point
如果该类型具有string或number索引签名,keyof则将返回这些类型:
type Arrayish = { [n: number]: unknown }; type A = keyof Arrayish;
type A = number
type Mapish = { [k: string]: boolean }; type M = keyof Mapish;
type M = string | number
请注意,在此示例中,M
是 string | number
— 这是因为 JavaScript 对象键始终强制转换为字符串,因此obj[0]
始终与obj["0"]
.
keyof
当与映射类型结合使用时,类型变得特别有用,我们稍后会详细了解。
4.3 Typeof 类型运算符
JavaScript已经有了一个可以在表达式上下文中使用的typeof
运算符:
// Prints "string" console.log(typeof "Hello world");
TypeScript添加了一个typeof
运算符,您可以在类型上下文中使用该运算符来引用变量或属性的类型:
let s = "hello"; let n: typeof s;
let n: string
这对于基本类型不是很有用,但是结合其他类型操作符,您可以使用typeof
来方便地表达许多模式。举个例子,让我们从预定义类型ReturnType<T >
开始。它接受一个函数类型并产生它的返回类型:
type Predicate = (x: unknown) => boolean; type K = ReturnType<Predicate>;
type K = boolean
如果我们尝试在函数名上使用ReturnType
,我们会看到一个指导性的错误:
function f() { return { x: 10, y: 3 }; } type P = ReturnType<f>;
'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?
记住,值和类型不是一回事。为了引用值f的类型,我们使用typeof
:
function f() { return { x: 10, y: 3 }; } type P = ReturnType<typeof f>;
type P = {
x: number;
y: number;
}
限制
TypeScript 有意限制您可以使用typeof
的表达式类型。
具体来说,在标识符(即变量名)或它们的属性上使用typeof
是合法的。这有助于避免令人困惑的陷阱,即编写您认为正在执行但实际上没有执行的代码:
// Meant to use = ReturnType<typeof msgbox> let shouldContinue: typeof msgbox("Are you sure you want to continue?");
',' expected.
4.4 索引访问类型
我们可以使用索引访问类型来查找另一种类型的特定属性:
type Person = { age: number; name: string; alive: boolean }; type Age = Person["age"];
type Age = number
索引类型本身就是一种类型,因此我们可以完全使用联合keyof
、 或其他类型:
type I1 = Person["age" | "name"];
type I1 = string | number
type I2 = Person[keyof Person];
type I2 = string | number | boolean
type AliveOrName = "alive" | "name"; type I3 = Person[AliveOrName];
type I3 = string | boolean
如果您尝试索引不存在的属性,您甚至会看到错误:
type I1 = Person["alve"];
Property 'alve' does not exist on type 'Person'.
使用任意类型进行索引的另一个示例是number
用于获取数组元素的类型。我们可以结合它typeof
来方便地捕获数组字面量的元素类型:
const MyArray = [ { name: "Alice", age: 15 }, { name: "Bob", age: 23 }, { name: "Eve", age: 38 }, ]; type Person = typeof MyArray[number];
type Person = {
name: string;
age: number;
}
type Age = typeof MyArray[number]["age"];
type Age = number
// 或者 type Age2 = Person["age"];
type Age2 = number
索引时只能使用类型,这意味着不能使用const
进行变量引用:
const key = "age"; type Age = Person[key];
Type 'key' cannot be used as an index type. 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?
但是,您可以将类型别名用于类似样式的重构:
type key = "age"; type Age = Person[key];
4.5 条件类型
在最有用的程序的核心,我们必须根据输入做出决定。JavaScript 程序也不例外,但考虑到值很容易内省,这些决定也基于输入的类型。 条件类型有助于描述输入和输出类型之间的关系。
interface Animal { live(): void; } interface Dog extends Animal { woof(): void; } type Example1 = Dog extends Animal ? number : string;
type Example1 = number
type Example1 = Dog extends Animal ? number : string;
type Example1 = number
type Example2 = RegExp extends Animal ? number : string;
type Example2 = string
条件类型的形式有点像在JavaScript中的条件表达式( condition?trueExpression : falseExpression)
:
SomeType extends OtherType ? TrueType : FalseType;
当extends
左边的类型可赋给右边的类型时,那么你将在第一个分支(true
分支)中得到该类型;否则,您将在后一个分支(false
分支)中获得该类型。
从上面的例子来看,条件类型可能不会马上有用——我们可以告诉自己Dog是否扩展了Animal,并选择number
或string
!但是条件类型的强大之处在于和泛型一起使用。
例如,让我们采用下面的createLabel
函数:
interface IdLabel { id: number /* some fields */; } interface NameLabel { name: string /* other fields */; } function createLabel(id: number): IdLabel; function createLabel(name: string): NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel; function createLabel(nameOrId: string | number): IdLabel | NameLabel { throw "unimplemented"; }
createLabel
的这些重载描述了一个基于输入类型做出选择的JavaScript函数。请注意以下几点:
- 如果一个库必须在它的API中一遍又一遍地做同样的选择,这就变得很麻烦。
- 我们必须创建三个重载:一个用于我们确定类型的每种情况(一个用于
string
,一个用于number
),另一个用于最常见的情况(采用string | number
)。对于createLabel
可以处理的每一种新类型,重载的数量都会呈指数增长。
相反,我们可以将该逻辑编码为条件类型:
type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
然后,我们可以使用条件类型将重载简化为一个没有重载的函数。
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> { throw "unimplemented"; } let a = createLabel("typescript");
let a: NameLabel
let b = createLabel(2.8);
let b: IdLabel
let c = createLabel(Math.random() ? "hello" : 42);
let c: NameLabel | IdLabel
条件类型约束
通常,条件类型的检查会为我们提供一些新信息。就像使用类型保护缩小可以给我们一个更具体的类型一样,条件类型的真正分支将进一步限制我们检查的类型的泛型。
例如,让我们采取以下措施:
type MessageOf<T> = T["message"];
Type '"message"' cannot be used to index type 'T'.
在这个例子中,TypeScript 出错是因为T
不知道有一个名为message
. 我们可以约束T
:
type MessageOf<T extends { message: unknown }> = T["message"]; interface Email { message: string; } type EmailMessageContents = MessageOf<Email>;
type EmailMessageContents = string
但是,如果我们想MessageOf
采用任何类型,并且默认为never
某个message
属性不可用,该怎么办?我们可以通过将约束移出并引入条件类型来做到这一点:
type MessageOf<T> = T extends { message: unknown } ? T["message"] : never; interface Email { message: string; } interface Dog { bark(): void; } type EmailMessageContents = MessageOf<Email>;
type EmailMessageContents = string
type DogMessageContents = MessageOf<Dog>;
type DogMessageContents = never
在true分支内,TypeScript知道T
将有一个消息属性。
作为另一个例子,我们也可以编写一个名为Flatten
的类型,它将数组类型展平为它们的元素类型,但在其他情况下不处理它们:
type Flatten<T> = T extends any[] ? T[number] : T; // Extracts out the element type. type Str = Flatten<string[]>;
type Str = string
// Leaves the type alone. type Num = Flatten<number>;
type Num = number
当Flatten给定一个数组类型时,它使用索引访问number来获取string[]的元素类型。否则,它只返回给定的类型。
在条件类型中推断
我们刚刚发现自己使用条件类型来应用约束,然后提取类型。这最终成为一种常见的操作,条件类型使它更容易。
条件类型为我们提供了一种方法来推断我们使用infer关键字在真实分支中比较的类型。例如,我们可以推断元素类型,Flatten而不是使用索引访问类型“手动”获取它:
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
在这里,我们使用infer
关键字声明性地引入了一个新的泛型类型变量Item
,而不是指定如何T
在真正的分支中检索元素类型。这使我们不必考虑如何挖掘和探索我们感兴趣的类型的结构。
我们可以使用关键字infer
编写一些有用的辅助类型别名。例如,对于简单的情况,我们可以从函数类型中提取返回类型:
type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never; type Num = GetReturnType<() => number>;
type Num = number
type Str = GetReturnType<(x: string) => string>;
type Str = string
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;
type Bools = boolean[]
当从具有多个调用签名的类型(如重载函数的类型)进行推断时,将从最后一个签名进行推断(据推测,这是最宽松的总括情况)。不可能基于参数类型列表执行重载决策。
declare function stringOrNum(x: string): number; declare function stringOrNum(x: number): string; declare function stringOrNum(x: string | number): string | number; type T1 = ReturnType<typeof stringOrNum>;
type T1 = string | number
分布式条件类型
当条件类型作用于泛型类型时,它们在给定联合类型时变得可分配。例如,采取以下措施:
type ToArray<Type> = Type extends any ? Type[] : never;
如果我们将联合类型插入ToArray
,那么条件类型将应用于该联合的每个成员。
type ToArray<Type> = Type extends any ? Type[] : never; type StrArrOrNumArr = ToArray<string | number>;
type StrArrOrNumArr = string[] | number[]
这里发生的情况是StrArrOrNumArr
分布在:
string | number;
并将联合的每个成员类型映射到有效的内容:
ToArray<string> | ToArray<number>;
这给我们留下了:
string[] | number[];
典型地,分配性是期望的行为。为了避免这种行为,可以用方括号将extends
关键字的两边括起来。
type ToArrayNonDist<Type> = [Type] extends [any] ? Type[] : never; // 'StrArrOrNumArr' is no longer a union. type StrArrOrNumArr = ToArrayNonDist<string | number>;
type StrArrOrNumArr = (string | number)[]
4.6 映射类型 (Mapped Types)
当你不想重复自己的时候,有时候一种类型需要建立在另一种类型的基础上。
映射类型建立在索引签名语法的基础上,索引签名用于声明尚未提前声明的属性类型:
type OnlyBoolsAndHorses = { [key: string]: boolean | Horse; }; const conforms: OnlyBoolsAndHorses = { del: true, rodney: false, };
映射类型是一种泛型类型,它使用PropertyKeys的联合(通常通过keyof创建)来循环访问键以创建类型:
type OptionsFlags<Type> = { [Property in keyof Type]: boolean; };
在此示例中,OptionsFlags将从type Type中获取所有属性,并将它们的值更改为布尔值。
type FeatureFlags = { darkMode: () => void; newUserProfile: () => void; }; type FeatureOptions = OptionsFlags<FeatureFlags>;
type FeatureOptions = {
darkMode: boolean;
newUserProfile: boolean;
}
映射修饰符
有两个额外的修饰符可以在映射过程中应用:readonly
和?
它们分别影响可变性和可选性。
您可以通过添加前缀-
或+
来删除或添加这些修饰符。如果不加前缀,那么就假设+
了。
// Removes 'readonly' attributes from a type's properties type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>;
type UnlockedAccount = {
id: string;
name: string;
}
// Removes 'optional' attributes from a type's properties type Concrete<Type> = { [Property in keyof Type]-?: Type[Property]; }; type MaybeUser = { id: string; name?: string; age?: number; }; type User = Concrete<MaybeUser>;
type User = {
id: string;
name: string;
age: number;
}
通过as
重新映射键
在TypeScript 4.1及更高版本中,可以使用映射类型中的as
子句重新映射映射类型中的键:
type MappedTypeWithNewProperties<Type> = { [Properties in keyof Type as NewKeyType]: Type[Properties] }
您可以利用模板文字类型等特性,从以前的属性名创建新的属性名:
type Getters<Type> = { [Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property] }; interface Person { name: string; age: number; location: string; } type LazyPerson = Getters<Person>;
type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}
您可以通过条件类型生成never
来过滤掉键:
// Remove the 'kind' property type RemoveKindField<Type> = { [Property in keyof Type as Exclude<Property, "kind">]: Type[Property] }; interface Circle { kind: "circle"; radius: number; } type KindlessCircle = RemoveKindField<Circle>;
type KindlessCircle = {
radius: number;
}
您可以映射任意的联合,不仅仅是string | number | symbol
的联合,而是任何类型的联合:
type EventConfig<Events extends { kind: string }> = { [E in Events as E["kind"]]: (event: E) => void; } type SquareEvent = { kind: "square", x: number, y: number }; type CircleEvent = { kind: "circle", radius: number }; type Config = EventConfig<SquareEvent | CircleEvent>
type Config = {
square: (event: SquareEvent) => void;
circle: (event: CircleEvent) => void;
}
4.7 模板文字类型
模板文字类型建立在字符串文字类型之上,并且能够通过联合扩展成许多字符串。
它们与JavaScript 中的模板文字字符串具有相同的语法,但用于类型位置。当与具体文字类型一起使用时,模板文字通过连接内容来生成新的字符串文字类型。
如:
type World = "world"; type Greeting = `hello ${World}`;
type Greeting = “hello world”
当在插值位置使用联合时,类型是可以由每个联合成员表示的每个可能的字符串文字的集合:
type EmailLocaleIDs = "welcome_email" | "email_heading"; type FooterLocaleIDs = "footer_title" | "footer_sendoff"; type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type AllLocaleIDs = “welcome_email_id” | “email_heading_id” | “footer_title_id” | “footer_sendoff_id”
对于模板文字中的每个插值位置,联合是交叉相乘的:
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; type Lang = "en" | "ja" | "pt"; type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;
type LocaleMessageIDs = “en_welcome_email_id” | “en_email_heading_id” | “en_footer_title_id” | “en_footer_sendoff_id” | “ja_welcome_email_id” | “ja_email_heading_id” | “ja_footer_title_id” | “ja_footer_sendoff_id” | “pt_welcome_email_id” | “pt_email_heading_id” | “pt_footer_title_id” | “pt_footer_sendoff_id”
我们通常建议人们对大型字符串联合使用提前生成,但这在较小的情况下很有用。
类型中的字符串联合
当基于类型内的信息定义一个新字符串时,模板文字的力量就来了。
考虑一个函数(makeWatchedObject
)向传递的对象添加一个名为on()
的新函数的情况。在JavaScript中,它的调用可能就像:makeWatchedObject(baseObject)
。我们可以把基本对象想象成这样:
const passedObject = { firstName: "Saoirse", lastName: "Ronan", age: 26, };
将添加到基对象的on函数需要两个参数,一个eventName(字符串)和一个callBack(函数)。
eventName
的格式应为attributeInThePassedObject+" Changed "
;因此,firstNameChanged
从基对象中的属性firstName
派生而来。
回调函数在被调用时:
- 应传递一个与
attributeInThePassedObject
名称关联的类型值;因此,由于firstName
的类型为string
,firstNameChanged
事件的回调期望在调用时向其传递一个字符串。类似地,与年龄相关的事件应该用一个数字参数来调用 - 应该具有
void
返回类型(为了简化演示)
on()
的朴素函数签名可能是:on(eventName: string,callBack: (newValue: any) => void)
。然而,在前面的描述中,我们确定了想要在代码中记录的重要类型约束。模板文字类型让我们将这些约束带入代码中。
const person = makeWatchedObject({ firstName: "Saoirse", lastName: "Ronan", age: 26, }); // makeWatchedObject has added `on` to the anonymous Object person.on("firstNameChanged", (newValue) => { console.log(`firstName was changed to ${newValue}!`); });
请注意,on
监听事件“firstNameChanged”
,而不仅仅是“firstName”
。如果我们要确保合格事件名称的集合受到被监视对象中属性名称的联合约束,并在末尾添加“Changed ”,那么我们对on()
的简单规范将变得更加健壮。虽然我们在JavaScript中做这样的计算很舒服,例如Object.keys(passedObject).map(x =>
${x}Changed)
,类型系统中的模板文字提供了一种类似的字符串操作方法:
type PropEventSource<Type> = { on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void; }; /// Create a "watched object" with an 'on' method /// so that you can watch for changes to properties. declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;
有了这个,我们可以构建一些在给定错误属性时出错的东西:
const person = makeWatchedObject({ firstName: "Saoirse", lastName: "Ronan", age: 26 }); person.on("firstNameChanged", () => {}); // Prevent easy human error (using the key instead of the event name) person.on("firstName", () => {});
Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
// It's typo-resistant person.on("frstNameChanged", () => {});
Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.
5. 其它一些预定义的类型
5.1 实用程序类型
5.1.1 Partial
使T中的所有属性都可选。构造一个所有属性都Type设置为可选的类型。此实用程序将返回一个表示给定类型的所有子集的类型。
type Partial<T> = { [P in keyof T]?: T[P]; };
例如:
interface Todo { title: string; description: string; } function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) { return { ...todo, ...fieldsToUpdate }; } const todo1 = { title: "organize desk", description: "clear clutter", }; const todo2 = updateTodo(todo1, { description: "throw out trash", });
5.1.2 Required
使T中的所有属性成为必需的。Type构造一个由set to required的所有属性组成的类型。的反面Partial。
type Required<T> = { [P in keyof T]-?: T[P]; };
例如:
interface Props { a?: number; b?: string; } const obj: Props = { a: 5 }; const obj2: Required<Props> = { a: 5 };
Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
5.1.3 Readonly
将T中的所有属性设为只读
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
例如:
interface Todo { title: string; } const todo: Readonly<Todo> = { title: "Delete inactive users", }; todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.
该实用程序可用于表示在运行时将失败的赋值表达式(即尝试重新分配冻结对象的属性时)。
Object.freeze:
function freeze<Type>(obj: Type): Readonly<Type>;
5.1.4 Record<K extends keyof any, T>
用类型T的一组属性K构造一个类型。构造一个对象类型,其属性键为Keys,其属性值为Type。此实用程序可用于将一种类型的属性映射到另一种类型。
type Record<K extends keyof any, T> = { [P in K]: T; };
例如:
interface CatInfo { age: number; breed: string; } type CatName = "miffy" | "boris" | "mordred"; const cats: Record<CatName, CatInfo> = { miffy: { age: 10, breed: "Persian" }, boris: { age: 5, breed: "Maine Coon" }, mordred: { age: 16, breed: "British Shorthair" }, }; cats.boris;
const cats: Record<CatName, CatInfo>
5.1.5 Pick<T, K extends keyof T>
从 T 中挑选一组其键在 联合 K 中的属性。构造一个所有属性都Type设置为的类型readonly,这意味着构造类型的属性不能重新分配。通过从 中选择一组属性Keys(字符串文字或字符串文字的并集)来构造类型Type。
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
例子:
interface Todo { title: string; description: string; completed: boolean; } type TodoPreview = Pick<Todo, "title" | "completed">; const todo: TodoPreview = { title: "Clean room", completed: false, }; todo;
const todo: TodoPreview
5.1.6 Omit<Type, Keys>
Type通过从中选择所有属性然后删除Keys(字符串文字或字符串文字的联合)来构造类型。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
例子:
interface Todo { title: string; description: string; completed: boolean; createdAt: number; } type TodoPreview = Omit<Todo, "description">; const todo: TodoPreview = { title: "Clean room", completed: false, createdAt: 1615544252770, }; todo;
const todo: TodoPreview
type TodoInfo = Omit<Todo, "completed" | "createdAt">; const todoInfo: TodoInfo = { title: "Pick up kids", description: "Kindergarten closes at 5pm", }; todoInfo;
const todoInfo: TodoInfo
5.1.7 Exclude<UnionType, ExcludedMembers>
UnionType通过从所有可分配给 的联合成员中排除来构造一个类型ExcludedMembers。
- 从T中排除那些可赋给U的类型
type Exclude<T, U> = T extends U ? never : T;
- 从T中提取那些可以赋给U的类型
type Extract<T, U> = T extends U ? T : never;
例如:
type T0 = Exclude<"a" | "b" | "c", "a">;
type T0 = “b” | “c”
type T1 = Exclude<"a" | "b" | "c", "a" | "b">;
type T1 = “c”
type T2 = Exclude<string | number | (() => void), Function>;
type T2 = string | number
5.1.8 Extract<Type, Union>
Type通过从可分配给 的所有联合成员中提取来构造一个类型Union。
例如:
type T0 = Extract<"a" | "b" | "c", "a" | "f">;
type T0 = “a”
type T1 = Extract<string | number | (() => void), Function>;
type T1 = () => void
5.1.9 NonNullable
type NonNullable<T> = T extends null | undefined ? never : T;
5.1.10 Parameters<T extends (…args: any) => any>
获取元组中函数类型的参数。从函数类型的参数中使用的类型构造元组类型Type。
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
例如:
declare function f1(arg: { a: number; b: string }): void; type T0 = Parameters<() => string>;
type T0 = []
type T1 = Parameters<(s: string) => void>;
type T1 = [s: string]
type T2 = Parameters<<T>(arg: T) => T>;
type T2 = [arg: unknown]
type T3 = Parameters<typeof f1>;
type T3 = [arg: {
a: number;
b: string;
}]
type T4 = Parameters<any>;
type T4 = unknown[]
type T5 = Parameters<never>;
type T5 = never
type T6 = Parameters<string>;
Type 'string' does not satisfy the constraint '(...args: any) => any'.
type T6 = never
type T7 = Parameters<Function>;
Type 'Function' does not satisfy the constraint '(...args: any) => any'. Type 'Function' provides no match for the signature '(...args: any): any'.
type T7 = never
5.1.11 ConstructorParameters<Type extends abstract new (…args: any) => any>
获取元组中构造函数类型的参数。从构造函数类型的类型构造元组或数组类型。它产生一个包含所有参数类型的元组类型(或者never如果Type不是函数的类型)。
type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;
例子:
type T0 = ConstructorParameters<ErrorConstructor>;
type T0 = [message?: string]
type T1 = ConstructorParameters<FunctionConstructor>;
type T1 = string[]
type T2 = ConstructorParameters<RegExpConstructor>;
type T2 = [pattern: string | RegExp, flags?: string]
type T3 = ConstructorParameters<any>;
type T3 = unknown[]
type T4 = ConstructorParameters<Function>;
Type 'Function' does not satisfy the constraint 'abstract new (...args: any) => any'. Type 'Function' provides no match for the signature 'new (...args: any): any'.
type T4 = never
5.1.12 ReturnType<T extends (…args: any) => any>
获取函数类型的返回类型。构造一个由 function 的返回类型组成的类型Type。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
例如:
declare function f1(): { a: number; b: string }; type T0 = ReturnType<() => string>;
type T0 = string
type T1 = ReturnType<(s: string) => void>;
type T1 = void
type T2 = ReturnType<<T>() => T>;
type T2 = unknown
type T3 = ReturnType<<T extends U, U extends number[]>() => T>;
type T3 = number[]
type T4 = ReturnType<typeof f1>;
type T4 = {
a: number;
b: string;
}
type T5 = ReturnType<any>;
type T5 = any
type T6 = ReturnType<never>;
type T6 = never
type T7 = ReturnType<string>;
Type 'string' does not satisfy the constraint '(...args: any) => any'.
type T7 = any
type T8 = ReturnType<Function>;
Type 'Function' does not satisfy the constraint '(...args: any) => any'. Type 'Function' provides no match for the signature '(...args: any): any'.
type T8 = any
5.1.13 InstanceType<T extends abstract new (…args: any) => any>
获取构造函数类型的返回类型
type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;
构造一个类型,该类型由 中的构造函数的实例类型组成Type。
5.1.14 ThisParameterType
提取函数类型的 this
参数的类型,如果函数类型没有 this
参数,则为 unknown
。
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;
5.1.15 OmitThisParameter
从函数类型中移除this
参数。如果Type
没有显式声明此参数,则结果只是Type
。否则,将从Type`创建一个不带此参数的新函数类型。泛型被删除,只有最后一个重载签名被传播到新的函数类型中。
type OmitThisParameter<T> = unknown extends ThisParameterType<T> ? T : T extends (...args: infer A) => infer R ? (...args: A) => R : T;
例子:
function toHex(this: Number) { return this.toString(16); } const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5); console.log(fiveToHex());
5.1.16 ThisType
上下文this
类型的标记。此实用程序不返回转换后的类型。相反,它用作上下文this类型的标记。请注意,noImplicitThis必须启用该标志才能使用此实用程序。
interface ThisType<T> { }
例如:
type ObjectDescriptor<D, M> = { data?: D; methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M }; function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M { let data: object = desc.data || {}; let methods: object = desc.methods || {}; return { ...data, ...methods } as D & M; } let obj = makeObject({ data: { x: 0, y: 0 }, methods: { moveBy(dx: number, dy: number) { this.x += dx; // Strongly typed this this.y += dy; // Strongly typed this }, }, }); obj.x = 10; obj.y = 20; obj.moveBy(5, 5);
在上面的示例中,methodsto 参数中的对象makeObject具有上下文类型,该类型包括ThisType<D & M>,因此对象内方法中的this的类型是。请注意属性的类型如何同时是推理目标和方法中类型的来源。methods{ x: number, y: number } & { moveBy(dx: number, dy: number): number }methodsthis
ThisType标记接口只是一个在 中声明的空接口lib.d.ts。除了在对象文字的上下文类型中被识别之外,接口的行为就像任何空接口。
5.2 内在字符串操作类型
5.2.1 Uppercase
将字符串文字类型转换为大写
type Uppercase<S extends string> = intrinsic;
例子
type Greeting = "Hello, world" type ShoutyGreeting = Uppercase<Greeting> // type ShoutyGreeting = "HELLO, WORLD" type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}` type MainID = ASCIICacheKey<"my_app"> // 其中类型 MainID = "ID-MY_APP"
5.2.2 Lowercase
将字符串文字类型转换为小写
type Lowercase<S extends string> = intrinsic;
例子
type Greeting = "Hello, world" type QuietGreeting = Lowercase<Greeting> // 其中类型 QuietGreeting = "hello, world" type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}` type MainID = ASCIICacheKey<"MY_APP"> // 其中类型 MainID = "id-my_app"
5.2.3 Capitalize
将字符串类型的第一个字符转换为大写
type Capitalize<S extends string> = intrinsic;
例子:
type LowercaseGreeting = "hello, world"; type Greeting = Capitalize<LowercaseGreeting>; // 其中类型 type Greeting = "Hello, world"
5.2.4 Uncapitalize
将字符串类型的第一个字符转换为小写
type Uncapitalize<S extends string> = intrinsic;
例子:
type LowercaseGreeting = "hello, world"; type Greeting = Capitalize<LowercaseGreeting>; // 其中类型 type Greeting = "Hello, world"