TypeScript基礎入門之高階型別的索引型別(Index types)
高階型別
索引型別(Index types)
使用索引型別,編譯器就能夠檢查使用了動態屬性名的程式碼。 例如,一個常見的JavaScript模式是從物件中選取屬性的子集。
function pluck(o, names) { return names.map(n => o[n]); }
下面是如何在TypeScript裡使用此函式,通過 索引型別查詢和 索引訪問操作符:
function pluck<T, K extends keyof T>(o:T, names: K[]): T[K][] { return names.map(n => o[n]) } interface Interface1 { name: string; age: number; } let i: Interface1 = { name: "A", age: 1, } let pluckStr: string[] = pluck(i, ['name']) console.log(pluckStr)
執行後輸出如下
[ 'A' ]
編譯器會檢查 name是否真的是Interface1的一個屬性。 本例還引入了幾個新的型別操作符。 首先是 keyof T, 索引型別查詢操作符。 對於任何型別 T, keyof T的結果為 T上已知的公共屬性名的聯合。 例如:
let someProps: keyof Interface1; // 'name' | 'age'
keyof Interface1是完全可以與'name'|'age'互相替換的。 不同的是如果你添加了其它的屬性到Interface1,例如address: string,那麼 keyof Interface1會自動變為'name'|'age'|'address'。 你可以在像pluck函式這類上下文裡使用 keyof,因為在使用之前你並不清楚可能出現的屬性名。 但編譯器會檢查你是否傳入了正確的屬性名給 pluck:
pluck(i, ['age', 'unknown']); // error, 'unknown' is not in 'name' | 'age'
第二個操作符是T[K],索引訪問操作符。 在這裡,型別語法反映了表示式語法。 這意味著 i['name']具有型別Interface1['name'] — 在我們的例子裡則為string型別。 然而,就像索引型別查詢一樣,你可以在普通的上下文裡使用 T[K],這正是它的強大所在。 你只要確保型別變數 K extends keyof T就可以了。 例如下面 getProperty函式的例子:
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] { return o[name]; // o[name] is of type T[K] }
getProperty裡的 o: T和 name: K,意味著 o[name]: T[K]。 當你返回 T[K]的結果,編譯器會例項化鍵的真實型別,因此 getProperty的返回值型別會隨著你需要的屬性改變。
let name: string = getProperty(i, 'name'); let age: number = getProperty(i, 'age'); let unknown = getProperty(i, 'unknown'); // error, 'unknown' is not in 'name' | 'age'
索引型別和字串索引簽名
keyof和T[K]與字串索引簽名進行互動。如果你有一個帶有字串索引簽名的型別,那麼 keyof T會是 string。並且T[string]為索引簽名的型別:
interface Map<T> { [key: string]: T; } let keys: keyof Map<number>; // string let value: Map<number>['foo']; // number