TypeScript 之 Indexed Access Types

冴羽 發表於 2021-12-01

前言

TypeScript 的官方文件早已更新,但我能找到的中文文件都還停留在比較老的版本。所以對其中新增以及修訂較多的一些章節進行了翻譯整理。

本篇整理自 TypeScript Handbook 中 「Indexed Access Types」 章節。

本文並不嚴格按照原文翻譯,對部分內容也做了解釋補充。

正文

我們可以使用索引訪問型別(indexed access type)查詢另外一個型別上的特定屬性:

type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"];
// type Age = number

因為索引名本身就是一個型別,所以我們也可以使用聯合、keyof 或者其他型別:

type I1 = Person["age" | "name"];  
// type I1 = string | number
 
type I2 = Person[keyof Person];
// type I2 = string | number | boolean
 
type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName];  
// type I3 = string | boolean

如果你嘗試查詢一個不存在的屬性,TypeScript 會報錯:

type I1 = Person["alve"];
// Property 'alve' does not exist on type 'Person'.

接下來是另外一個示例,我們使用 number 來獲取陣列元素的型別。結合 typeof 可以方便的捕獲陣列字面量的元素型別:

const MyArray = [
  { name: "Alice", age: 15 },
  { name: "Bob", age: 23 },
  { name: "Eve", age: 38 },
];
 
type Person = typeof MyArray[number];
       
// type Person = {
//    name: string;
//    age: number;
// }

type Age = typeof MyArray[number]["age"];  
// type Age = number

// Or
type Age2 = Person["age"];   
// type Age2 = number

作為索引的只能是型別,這意味著你不能使用 const 建立一個變數引用:

const key = "age";
type Age = Person[key];

// Type 'key' cannot be used as an index type.
// 'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?

然而你可以使用型別別名實現類似的重構:

type key = "age";
type Age = Person[key];

最後講一個實戰案例:​

假設有這樣一個業務場景,一個頁面要用在不同的 APP 裡,比如淘寶、天貓、支付寶,根據所在 APP 的不同,呼叫的底層 API 會不同,我們可能會這樣寫:

const APP = ['TaoBao', 'Tmall', 'Alipay'];

function getPhoto(app: string) {
  // ...
}
  
getPhoto('TaoBao'); // ok
getPhoto('whatever'); // ok

如果我們僅僅是對 app 約束為 string 型別,即使傳入其他的字串,也不會導致報錯,我們可以使用字面量聯合型別約束一下:

const APP = ['TaoBao', 'Tmall', 'Alipay'];
type app = 'TaoBao' | 'Tmall' | 'Alipay';

function getPhoto(app: app) {
  // ...
}
  
getPhoto('TaoBao'); // ok
getPhoto('whatever'); // not ok

但寫兩遍又有些冗餘,我們怎麼根據一個陣列獲取它的所有值的字串聯合型別呢?我們就可以結合上一篇的 typeof 和本節的內容實現:

const APP = ['TaoBao', 'Tmall', 'Alipay'] as const;
type app = typeof APP[number];
// type app = "TaoBao" | "Tmall" | "Alipay"

function getPhoto(app: app) {
  // ...
}
  
getPhoto('TaoBao'); // ok
getPhoto('whatever'); // not ok

我們來一步步解析:

首先是使用 as const 將陣列變為 readonly 的元組型別:

const APP = ['TaoBao', 'Tmall', 'Alipay'] as const;
// const APP: readonly ["TaoBao", "Tmall", "Alipay"]

但此時 APP 還是一個值,我們通過 typeof 獲取 APP 的型別:

type typeOfAPP = typeof APP;
// type typeOfAPP = readonly ["TaoBao", "Tmall", "Alipay"]

最後在通過索引訪問型別,獲取字串聯合型別:

type app = typeof APP[number];
// type app = "TaoBao" | "Tmall" | "Alipay"

TypeScript 系列

  1. TypeScript 之 Narrowing
  2. TypeScript 之 More on Functions
  3. TypeScript 之 Object Type
  4. TypeScript 之 Generics
  5. TypeScript 之 Keyof Type Operator
  6. TypeScript 之 Typeof Type Operator

微信:「mqyqingfeng」,加我進冴羽唯一的讀者群。

如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝。如果喜歡或者有所啟發,歡迎 star,對作者也是一種鼓勵。