做者:Marius Schulz
译者:前端小智
来源:Marius Schulz
干货系列文章汇总以下,以为不错点个Star:前端
Github: https://github.com/qq44924588...](https://github.com/qq44924588...git
为了保证的可读性,本文采用意译而非直译。github
TypeScript 2.1 引入了映射类型,这是对类型系统的一个强大的补充。本质上,映射类型容许w我们经过映射属性类型从现有类型建立新类型。根据我们指定的规则转换现有类型的每一个属性。转换后的属性组成新的类型。算法
使用映射类型,能够捕获类型系统中相似 Object.freeze()
等方法的效果。冻结对象后,就不能再添加、更改或删除其中的属性。来看看如何在不使用映射类型的状况下在类型系统中对其进行编码:typescript
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
定义为只读属性。segmentfault
freezePoint
函数接受一个 Point
做为参数并冻结该参数,接着,向调用者返回相同的对象。然而,该对象的类型已更改成FrozenPoint
,所以其属性被静态类型化为只读。这就是为何当试图将 42
赋值给 x
属性时,TypeScript
会出错。在运行时,分配要么抛出一个类型错误(严格模式),要么静默失败(非严格模式)。 api
虽然上面的示例能够正确地编译和工做,但它有两大缺点微信
Point
类型以外,还必须定义 FrozenPoint
类型,这样才能将 readonly
修饰符添加到两个属性中。当我们更改 Point
时,还必须更改FrozenPoint
,这很容易出错,也很烦人。Object.freeze()
。来看看 Object.freeze()
是如何在 lib.d.ts
文件中定义的:app
/** * 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] };
这个语法一开始可能会让人望而生畏,我们来一步一步分析它:
T
的类型参数定义了一个泛型 Readonly。keyof
操做符。keyof T
将 T
类型的全部属性名表示为字符串字面量类型的联合。in
关键字表示咱们正在处理映射类型。[P in keyof T]: T[P]
表示将 T
类型的每一个属性 P
的类型转换为 T[P]
。若是没有readonly
修饰符,这将是一个身份转换。T[P]
是一个查找类型,它表示类型 T
的属性 P
的类型。readonly
修饰符指定每一个属性都应该转换为只读属性。由于 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 :
setState
方法容许我们更新整个状态或其中的一个子集。我们能够更新任意多个属性,这使得setState
方法成为 Partial<T>
的一个很好的用例。pick
函数从一个对象中选择一组属性。该方法返回一个新对象,该对象只包含我们选择的属性。可使用
Pick<T> 对
该行为进行构建,正如其名称所示。字符串、数字和布尔字面量类型(如:"abc"
,1
和true
)以前仅在存在显式类型注释时才被推断。从 TypeScript 2.1 开始,字面量类型老是推断为默认值。在 TypeScript 2.0 中,类型系统扩展了几个新的字面量类型:
boolean
字面量类型不带类型注解的 const
变量或 readonly
属性的类型推断为字面量初始化的类型。已经初始化且不带类型注解的 let
变量、var
变量、形参或非 readonly
属性的类型推断为初始值的扩展字面量类型。字符串字面量扩展类型是 string
,数字字面量扩展类型是number
,true
或 false
的字面量类型是 boolean
,还有枚举字面量扩展类型是枚举。
我们从局部变量和 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
函数:
Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'
固然,若是相应的参数只容许两个特定的字符串值,则不容许将任意字符串做为函数参数传递。可是,当为两个常量推断字面量类型“GET”
和“POST”
时,一切就都解决了。
编辑中可能存在的bug无法实时知道,过后为了解决这些bug,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug。
原文:
https://mariusschulz.com/blog...
https://mariusschulz.com/blog...
干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。