做者: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
由於篇幅的限制,今天的分享只到這裏。若是你們想了解更多的內容的話,能夠去掃一掃每篇文章最下面的二維碼,而後關注我們的微信公衆號,瞭解更多的資訊和有價值的內容。