在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
概述TypeScript 2.1 引入了映射类型,这是对类型系统的一个强大的补充。本质上,映射类型允许w咱们通过映射属性类型从现有类型创建新类型。根据咱们指定的规则转换现有类型的每个属性。转换后的属性组成新的类型。 使用映射类型,可以捕获类型系统中类似Object.freeze()等方法的效果。冻结对象后,就不能再添加、更改或删除其中的属性。来看看如何在不使用映射类型的情况下在类型系统中对其进行编码: interface Point { x: number; y: number; } interface FrozenPoint { readonly x: number; readonly y: number; } function freezePoint(p: Point): FrozenPoint { return Object.freeze(p); } const origin = freezePoint({ x: 0, y: 0 }); // Error! Cannot assign to 'x' because it // is a constant or a read-only property. origin.x = 42; 咱们定义了一个包含x和y两个属性的Point接口,咱们还定义了另一个接口FrozenPoint,它与Point相同,只是它的所有属性都被使用readonly定义为只读属性。 freezePoint函数接受一个Point作为参数并冻结该参数,接着,向调用者返回相同的对象。然而,该对象的类型已更改为FrozenPoint,因此其属性被静态类型化为只读。这就是为什么当试图将42赋值给x属性时,TypeScript会出错。在运行时,分配要么抛出一个类型错误(严格模式),要么静默失败(非严格模式)。 虽然上面的示例可以正确地编译和工作,但它有两大缺点
使用映射类型构建 Object.freeze()来看看Object.freeze()是如何在lib.d.ts文件中定义的: /** * Prevents the modification of existing property attributes and values, and prevents the addition of new properties. * @param o Object on which to lock the attributes. */ freeze<T>(o: T): Readonly<T>; 该方法的返回类型为Readonly<T>,这是一个映射类型,它的定义如下: type Readonly<T> = { readonly [P in keyof T]: T[P] }; 这个语法一开始可能会让人望而生畏,咱们来一步一步分析它:
因为Readonly<T>类型是泛型的,所以咱们为T提供的每种类型都正确地入了Object.freeze()中。 const origin = Object.freeze({ x: 0, y: 0 }); // Error! Cannot assign to 'x' because it // is a constant or a read-only property. origin.x = 42; 映射类型的语法更直观解释这次咱们使用Point类型为例来粗略解释类型映射如何工作。请注意,以下只是出于解释目的,并不能准确反映TypeScript使用的解析算法。 从类型别名开始: type ReadonlyPoint = Readonly<Point>; 现在,咱们可以在Readonly<T>中为泛型类型T的替换Point类型: type ReadonyPoint = { readonly [P in keyof Point]: Point[P] }; 现在咱们知道T是Point,可以确定keyof Point表示的字符串字面量类型的并集: type ReadonlyPoint = { readonly [P in "x" | "y"]: Point[p] }; 类型P表示每个属性x和y,咱们把它们作为单独的属性来写,去掉映射的类型语法 type ReadonlyPoint = { readonly x: Point["x"]; readonly y: Point["y"]; }; 最后,咱们可以解析这两种查找类型,并将它们替换为具体的x和y类型,这两种类型都是number。 type ReadonlyPoint = { readonly x: number; readonly y: number; }; 最后,得到的ReadonlyPoint类型与咱们手动创建的FrozenPoint类型相同。 更多映射类型的示例上面已经看到lib.d.ts文件中内置的Readonly <T>类型。此外,TypeScript 定义了其他映射类型,这些映射类型在各种情况下都非常有用。如下: /** * Make all properties in T optional */ type Partial<T> = { [P in keyof T]?: T[P] }; /** * From T pick a set of properties K */ type Pick<T, K extends keyof T> = { [P in K]: T[P] }; /** * Construct a type with a set of properties K of type T */ type Record<K extends string, T> = { [P in K]: T }; 这里还有两个关于映射类型的例子,如果需要的话,可以自己编写: /** * Make all properties in T nullable */ type Nullable<T> = { [P in keyof T]: T[P] | null }; /** * Turn all properties of T into strings */ type Stringify<T> = { [P in keyof T]: string }; 映射类型和联合的组合也是很有趣: type X = Readonly<Nullable<Stringify<Point>>>; // type X = { // readonly x: string | null; // readonly y: string | null; // }; 映射类型的实际用例实战中经常可以看到映射类型,来看看react和 Lodash :
更好的字面量类型推断字符串、数字和布尔字面量类型(如:"abc",1和true)之前仅在存在显式类型注释时才被推断。从 TypeScript 2.1 开始,字面量类型总是推断为默认值。在 TypeScript 2.0 中,类型系统扩展了几个新的字面量类型:
不带类型注解的const变量或readonly属性的类型推断为字面量初始化的类型。已经初始化且不带类型注解的let变量、var变量、形参或非readonly属性的类型推断为初始值的扩展字面量类型。字符串字面量扩展类型是string,数字字面量扩展类型是number,true或false的字面量类型是boolean,还有枚举字面量扩展类型是枚举。 更好的 const 变量推断咱们从局部变量和var关键字开始。当TypeScript看到下面的变量声明时,它会推断baseUrl变量的类型是string: var baseUrl = "https://example.com/"; // 推断类型: string 用let关键字声明的变量也是如此 let baseUrl = "https://example.com/"; // 推断类型: string 这两个变量都推断为string类型,因为它们可以随时更改。它们是用一个字面量字符串值初始化的,但是以后可以修改它们。 但是,如果使用const关键字声明变量并使用字符串字面量进行初始化,则推断的类型不再是string,而是字面量类型: const baseUrl = "https://example.com/"; // 推断类型: "https://example.com/" 由于常量字符串变量的值永远不会改变,因此推断出的类型会更加的具体。baseUrl变量无法保存"https://example.com/"以外的任何其他值。 字面量类型推断也适用于其他原始类型。如果用直接的数值或布尔值初始化常量,推断出的还是字面量类型: const HTTPS_PORT = 443; // 推断类型: 443 const rememberMe = true; // 推断类型: true 类似地,当初始化器是枚举值时,推断出的也是字面量类型: enum FlexDirection { Row, Column } const direction = FlexDirection.Column; // 推断类型: FlexDirection.Column 注意,direction类型为FlexDirection.Column,它是枚举字面量类型。如果使用let或var关键字来声明direction变量,那么它的推断类型应该是FlexDirection。 更好的只读属性推断与局部const变量类似,带有字面量初始化的只读属性也被推断为字面量类型: class ApiClient { private readonly baseUrl = "https://api.example.com/"; // 推断类型: "https://api.example.com/" get(endpoint: string) { // ... } } 只读类属性只能立即初始化,也可以在构造函数中初始化。试图更改其他位置的值会导致编译时错误。因此,推断只读类属性的字面量类型是合理的,因为它的值不会改变。 当然,TypeScript 不知道在运行时发生了什么:用readonly标记的属性可以在任何时候被一些js代码改变。readonly修饰符只限制从TypeScript代码中对属性的访问,在运行时就无能为力。也就是说,它会被编译时删除掉,不会出现在生成的js代码中。 推断字面量类型的有用性你可能会问自己,为什么推断const变量和readonly属性为字面量类型是有用的。考虑下面的代码: const HTTP_GET = "GET"; // 推断类型: "GET" const HTTP_POST = "POST"; // 推断类型: "POST" function get(url: string, method: "GET" | "POST") { // ... } get("https://example.com/", HTTP_GET); 如果推断HTTP_GET常量的类型是string而不是“GET”,则会出现编译时错误,因为无法将HTTP_GET作为第二个参数传递给get函数:
当然,如果相应的参数只允许两个特定的字符串值,则不允许将任意字符串作为函数参数传递。但是,当为两个常量推断字面量类型“GET”和“POST”时,一切就都解决了。 以上就是详解TypeScript映射类型和更好的字面量类型推断的详细内容,更多关于TS的资料请关注极客世界其它相关文章! |
请发表评论