詳解TS對象擴展運算符和rest運算符
概述
TypeScript 2.1 增加瞭對 對象擴展運算和 rest 屬性提案的支持,該提案在 ES2018 中標準化。可以以類型安全的方式使用 rest 和 spread 屬性。
對象 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元素中:
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方法返回一個元組數組,每個元組包含一個屬性鍵和相應的值。不可否認,在返回類型中有大量的方括號,但是我們一直在尋找類型安全性。
以上就是詳解TS對象擴展運算符和rest運算符的詳細內容,更多關於TS對象擴展運算符和rest運算符的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- 淺談typescript中keyof與typeof操作符用法
- 你可能不知道的typescript實用小技巧
- TypeScript語法詳解之類型操作的補充
- 淺談TypeScript 索引簽名的理解
- TypeScript基本類型之typeof和keyof詳解