【TypeScript 演化史 -- 6】對象擴展運算符和 rest 運算符及 keyof 和查找類型

做者:Marius Schulz前端

譯者:前端小智git

來源:Marius Schulzgithub

點贊再看,養成習慣
web


本文 GitHub:github.com/qq449245884… 上已經收錄,更多往期高贊文章的分類,也整理了不少個人文檔,和教程資料。歡迎Star和完善,你們面試能夠參照考點複習,但願咱們一塊兒有點東西。面試

爲了保證的可讀性,本文采用意譯而非直譯。typescript

TypeScript 2.1 增長了對 對象擴展運算和 rest 屬性提案的支持,該提案在 ES2018 中標準化。能夠以類型安全的方式使用 restspread 屬性。數組

對象 rest 屬性

假設已經定義了一個具備三個屬性的簡單字面量對象安全

const marius = {
  name: "Marius Schulz",
  website: "https://mariusschulz.com/",
  twitterHandle: "@mariusschulz"
};
複製代碼

使用 ES6 解構語法,能夠建立幾個局部變量來保存相應屬性的值。TypeScript 將正確地推斷每一個變量的類型:微信

const { name, website, twitterHandle } = marius;

name;          // Type string
website;       // Type string
twitterHandle; // Type string
複製代碼

這些都是正確的,但這到如今也啥新鮮的。除了提取感興趣的一組屬性以外,還可使用...語法將全部剩餘的屬性收集到rest元素中:app

const { twitterHandle, ...rest } = marius;

twitterHandle; // Type string
rest; // Type { name: string; website: string; }
複製代碼

TypeScript 會爲獲得結果的局部變量肯定正確的類型。雖然 twitterHandle 變量是一個普通的字符串,但 rest 變量是一個對象,其中包含剩餘兩個未被解構的屬性。

對象擴展屬性

假設我們但願使用 fetch() API 發出 HTTP 請求。它接受兩個參數:一個 URL 和一個 options 對象,options 包含請求的任何自定義設置。

在應用程序中,能夠封裝對fetch()的調用,並提供默認選項和覆蓋給定請求的特定設置。這些配置項相似以下:

const defaultOptions = {
  method: "GET",
  credentials: "same-origin"
};

const requestOptions = {
  method: "POST",
  redirect: "follow"
};
複製代碼

使用對象擴展,能夠將兩個對象合併成一個新對象,而後傳遞給 fetch() 方法

// Type { method: string; redirect: string; credentials: string; }
const options = {
  ...defaultOptions,
  ...requestOptions
};
複製代碼

對象擴展屬性建立一個新對象,複製 defaultOptions 中的全部屬性值,而後按照從左到右的順序複製requestOptions中的全部屬性值,最後獲得的結果以下:

console.log(options);
// {
//   method: "POST",
//   credentials: "same-origin",
//   redirect: "follow"
// }
複製代碼

請注意,分配順序很重要。若是一個屬性同時出如今兩個對象中,則後分配的會替換前面的。

固然,TypeScript 理解這種順序。所以,若是多個擴展對象使用相同的鍵定義一個屬性,那麼結果對象中該屬性的類型將是最後一次賦值的屬性類型,由於它覆蓋了先前賦值的屬性:

const obj1 = { prop: 42 };
const obj2 = { prop: "Hello World" };

const result1 = { ...obj1, ...obj2 }; // Type { prop: string }
const result2 = { ...obj2, ...obj1 }; // Type { prop: number }
複製代碼

製做對象的淺拷貝

對象擴展可用於建立對象的淺拷貝。假設咱但願經過建立一個新對象並複製全部屬性來從現有todo項建立一個新todo項,使用對象就能夠輕鬆作到:

const todo = {
  text: "Water the flowers",
  completed: false,
  tags: ["garden"]
};

const shallowCopy = { ...todo };
複製代碼

實際上,你會獲得一個新對象,全部的屬性值都被複制:

console.log(todo === shallowCopy);
// false

console.log(shallowCopy);
// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden"]
// }
複製代碼

如今能夠修改text屬性,但不會修改原始的todo項:

hallowCopy.text = "Mow the lawn";

console.log(shallowCopy);
// {
//   text: "Mow the lawn",
//   completed: false,
//   tags: ["garden"]
// }

console.log(todo);
// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden"]
// }
複製代碼

可是,新的todo項引用與第一個相同的 tags 數組。因爲是淺拷貝,改變數組將影響這兩個todo

shallowCopy.tags.push("weekend");

console.log(shallowCopy);
// {
//   text: "Mow the lawn",
//   completed: false,
//   tags: ["garden", "weekend"]
// }

console.log(todo);
// {
//   text: "Water the flowers",
//   completed: false,
//   tags: ["garden", "weekend"]
// }
複製代碼

若是想建立一個序列化對象的深拷貝,能夠考慮使用 JSON.parse(JSON.stringify(obj)) 或其餘方法,如 object.assign()。對象擴展僅拷貝屬性值,若是一個值是對另外一個對象的引用,則可能致使意外的行爲。

keyof 和查找類型

JS 是一種高度動態的語言。在靜態類型系統中捕獲某些操做的語義有時會很棘手。以一個簡單的 prop 函數爲例:

function prop(obj, key) {
  return obj[key];
}
複製代碼

它接受一個對象和一個鍵,並返回相應屬性的值。一個對象的不一樣屬性能夠有徹底不一樣的類型,我們甚至不知道 obj 是什麼樣子的。

那麼如何在 TypeScript 中編寫這個函數呢?先嚐試一下:

有了這兩個類型註釋,obj 必須是對象,key 必須是字符串。我們如今已經限制了兩個參數的可能值集。然而,TS 仍然推斷返回類型爲 any

const todo = {
  id: 1,
  text: "Buy milk",
  due: new Date(2016, 11, 31)
};

const id = prop(todo, "id");     // any
const text = prop(todo, "text"); // any
const due = prop(todo, "due");   // any
複製代碼

若是沒有更進一步的信息,TypeScript 就不知道將爲 key 參數傳遞哪一個值,因此它不能推斷出prop函數的更具體的返回類型。我們須要提供更多的類型信息來實現這一點。

keyof 操做符號

在 JS 中屬性名稱做爲參數的 API 是至關廣泛的,可是到目前爲止尚未表達在那些 API 中出現的類型關係。

TypeScript 2.1 新增長 keyof 操做符。輸入索引類型查詢或 keyof,索引類型查詢keyof T產生的類型是 T 的屬性名稱。假設我們已經定義瞭如下 Todo 接口:

interface Todo {
  id: number;
  text: string;
  due: Date;
}
複製代碼

各位能夠將 keyof 操做符應用於 Todo 類型,以得到其全部屬性鍵的類型,該類型是字符串字面量類型的聯合

type TodoKeys = keyof Todo; // "id" | "text" | "due"
複製代碼

固然,各位也能夠手動寫出聯合類型 "id" | "text" | "due",而不是使用 keyof,可是這樣作很麻煩,容易出錯,並且維護起來很麻煩。並且,它應該是特定於Todo類型的解決方案,而不是通用的解決方案。

索引類型查詢

有了 keyof,我們如今能夠改進 prop 函數的類型註解。咱們再也不但願接受任意字符串做爲 key 參數。相反,我們要求參數 key 實際存在於傳入的對象的類型上

function prop <T, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}
複製代碼

TypeScript 如今以推斷 prop 函數的返回類型爲 T[K],這個就是所謂的 索引類型查詢查找類型。它表示類型 T 的屬性 K 的類型。若是如今經過 prop 方法訪問下面 todo 的三個屬性,那麼每一個屬性都有正確的類型:

const todo = {
  id: 1,
  text: "Buy milk",
  due: new Date(2016, 11, 31)
};

const id = prop(todo, "id");     // number
const text = prop(todo, "text"); // string
const due = prop(todo, "due");   // Date
複製代碼

如今,若是傳遞一個 todo 對象上不存在的鍵會發生什麼

編譯器會報錯,這很好,它阻止我們試圖讀取一個不存在的屬性。

另外一個真實的示例,請查看與TypeScript編譯器一塊兒發佈的 lib.es2017.object.d.ts 類型聲明文件中Object.entries()方法:

interface ObjectConstructor {
  // ...
  entries<T extends { [key: string]: any }, K extends keyof T>(o: T): [keyof T, T[K]][];
  // ...
}
複製代碼

entries 方法返回一個元組數組,每一個元組包含一個屬性鍵和相應的值。不能否認,在返回類型中有大量的方括號,可是咱們一直在尋找類型安全性。


編輯中可能存在的bug無法實時知道,過後爲了解決這些bug,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

原文: mariusschulz.com/blog/object… mariusschulz.com/blog/keyof-…


交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

github.com/qq449245884…

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

相關文章
相關標籤/搜索