在 keyof 和對映型別中支援 number 和 symbol 命名的屬性
TypeScript 2.9 增加了對索引型別和對映型別中 number 和 symbol 命名屬性的支援。此前,keyof 運算子和對映型別僅支援 string 命名的屬性。
變更包括
- 對於某些型別
T,索引型別keyof T是string | number | symbol的子型別。 - 對映型別
{ [P in K]: XXX }允許任何可賦值給string | number | symbol的K。 - 在泛型
T物件的for...in語句中,迭代變數的推斷型別此前為keyof T,現在變為Extract<keyof T, string>。(換句話說,keyof T中僅包含類字串值的子集。)
給定一個物件型別 X,keyof X 的解析規則如下
- 如果
X包含字串索引簽名,則keyof X是string、number以及表示 symbol 類屬性的字面量型別的聯合,否則 - 如果
X包含數字索引簽名,則keyof X是number以及表示字串類和 symbol 類屬性的字面量型別的聯合,否則 keyof X是表示字串類、數字類和 symbol 類屬性的字面量型別的聯合。
其中
- 物件型別的字串類屬性是指那些使用識別符號、字串字面量或字串字面量型別的計算屬性名宣告的屬性。
- 物件型別的數字類屬性是指那些使用數字字面量或數字字面量型別的計算屬性名宣告的屬性。
- 物件型別的 symbol 類屬性是指那些使用唯一 symbol 型別(unique symbol type)的計算屬性名宣告的屬性。
在對映型別 { [P in K]: XXX } 中,K 中的每個字串字面量型別引入一個具有字串名稱的屬性,每個數字字面量型別引入一個具有數字名稱的屬性,每個唯一 symbol 型別引入一個具有唯一 symbol 名稱的屬性。此外,如果 K 包含 string 型別,則會引入字串索引簽名;如果 K 包含 number 型別,則會引入數字索引簽名。
示例
tsconst c = "c";const d = 10;const e = Symbol();const enum E1 {A,B,C,}const enum E2 {A = "A",B = "B",C = "C",}type Foo = {a: string; // String-like name5: string; // Number-like name[c]: string; // String-like name[d]: string; // Number-like name[e]: string; // Symbol-like name[E1.A]: string; // Number-like name[E2.A]: string; // String-like name};type K1 = keyof Foo; // "a" | 5 | "c" | 10 | typeof e | E1.A | E2.Atype K2 = Extract<keyof Foo, string>; // "a" | "c" | E2.Atype K3 = Extract<keyof Foo, number>; // 5 | 10 | E1.Atype K4 = Extract<keyof Foo, symbol>; // typeof e
由於 keyof 現在透過在鍵型別中包含 number 型別來反映數字索引簽名的存在,因此像 Partial<T> 和 Readonly<T> 這樣的對映型別在應用於帶有數字索引簽名的物件型別時可以正常工作。
tstype Arrayish<T> = {length: number;[x: number]: T;};type ReadonlyArrayish<T> = Readonly<Arrayish<T>>;declare const map: ReadonlyArrayish<string>;let n = map.length;let x = map[123]; // Previously of type any (or an error with --noImplicitAny)
此外,隨著 keyof 運算子對 number 和 symbol 命名鍵的支援,現在可以抽象訪問那些由數字字面量(例如數字列舉型別)和唯一 symbol 索引的物件屬性。
tsconst enum Enum {A,B,C,}const enumToStringMap = {[Enum.A]: "Name A",[Enum.B]: "Name B",[Enum.C]: "Name C",};const sym1 = Symbol();const sym2 = Symbol();const sym3 = Symbol();const symbolToNumberMap = {[sym1]: 1,[sym2]: 2,[sym3]: 3,};type KE = keyof typeof enumToStringMap; // Enum (i.e. Enum.A | Enum.B | Enum.C)type KS = keyof typeof symbolToNumberMap; // typeof sym1 | typeof sym2 | typeof sym3function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {return obj[key];}let x1 = getValue(enumToStringMap, Enum.C); // Returns "Name C"let x2 = getValue(symbolToNumberMap, sym3); // Returns 3
這是一項破壞性變更;此前,keyof 運算子和對映型別僅支援 string 命名的屬性。假設型別為 keyof T 的值始終是 string 的程式碼,現在將被標記為錯誤。
示例
tsfunction useKey<T, K extends keyof T>(o: T, k: K) {var name: string = k; // Error: keyof T is not assignable to string}
建議
-
如果您的函式只能處理字串命名的屬性鍵,請在宣告中使用
Extract<keyof T, string>tsfunction useKey<T, K extends Extract<keyof T, string>>(o: T, k: K) {var name: string = k; // OK} -
如果您的函式可以處理所有屬性鍵,那麼應該在下游進行相應的更改
tsfunction useKey<T, K extends keyof T>(o: T, k: K) {var name: string | number | symbol = k;} -
否則,請使用
keyofStringsOnly編譯器選項來停用此新行為。
JSX 元素中的泛型型別引數
JSX 元素現在允許將型別引數傳遞給泛型元件。
示例
tsclass GenericComponent<P> extends React.Component<P> {internalProp: P;}type Props = { a: number; b: string };const x = <GenericComponent<Props> a={10} b="hi" />; // OKconst y = <GenericComponent<Props> a={10} b={20} />; // Error
泛型標記模板中的泛型型別引數
標記模板(Tagged templates)是 ECMAScript 2015 引入的一種呼叫形式。與呼叫表示式一樣,泛型函式可以在標記模板中使用,TypeScript 將推斷所使用的型別引數。
TypeScript 2.9 允許將泛型型別引數傳遞給標記模板字串。
示例
tsdeclare function styledComponent<Props>(strs: TemplateStringsArray): Component<Props>;interface MyProps {name: string;age: number;}styledComponent<MyProps>`font-size: 1.5em;text-align: center;color: palevioletred;`;declare function tag<T>(strs: TemplateStringsArray, ...args: T[]): T;// inference fails because 'number' and 'string' are both candidates that conflictlet a = tag<string | number>`${100} ${"hello"}`;
import 型別
模組可以匯入其他模組中宣告的型別。但非模組的全域性指令碼無法訪問模組中宣告的型別。於是,import 型別登場了。
在型別註解中使用 import("mod"),無需顯式匯入即可獲取模組並訪問其匯出的宣告。
示例
假設在模組檔案中有一個類 Pet 的宣告
ts// module.d.tsexport declare class Pet {name: string;}
可以在非模組檔案 global-script.ts 中使用
ts// global-script.tsfunction adopt(p: import("./module").Pet) {console.log(`Adopting ${p.name}...`);}
這也適用於 JSDoc 註釋,以在 .js 檔案中引用其他模組的型別
js// a.js/*** @param p { import("./module").Pet }*/function walk(p) {console.log(`Walking ${p.name}...`);}
放寬聲明發射可見性規則
有了 import 型別,宣告檔案生成過程中報告的許多可見性錯誤都可以由編譯器處理,而無需更改輸入。
例如:
tsimport { createHash } from "crypto";export const hash = createHash("sha256");// ^^^^// Exported variable 'hash' has or is using name 'Hash' from external module "crypto" but cannot be named.
使用 TypeScript 2.9,不再報告錯誤,生成的檔案現在如下所示
tsexport declare const hash: import("crypto").Hash;
支援 import.meta
TypeScript 2.9 引入了對 import.meta 的支援,這是一種由當前 TC39 提案 所描述的新元屬性。
import.meta 的型別是全域性 ImportMeta 型別,定義在 lib.es5.d.ts 中。此介面極其有限。根據上下文,新增 Node 或瀏覽器的通用屬性需要介面合併,並可能需要全域性擴充。
示例
假設 __dirname 在 import.meta 上總是可用,宣告可以透過重新開啟 ImportMeta 介面來完成
ts// node.d.tsinterface ImportMeta {__dirname: string;}
使用方式如下
tsimport.meta.__dirname; // Has type 'string'
import.meta 僅在目標為 ESNext 模組和 ECMAScript 版本時才允許使用。
新的 --resolveJsonModule
Node.js 應用通常需要 .json 檔案。透過 TypeScript 2.9,resolveJsonModule 允許匯入、提取 .json 檔案的型別並生成它們。
示例
ts// settings.json{"repo": "TypeScript","dry": false,"debug": false}
ts// a.tsimport settings from "./settings.json";settings.debug === true; // OKsettings.dry === 2; // Error: Operator '===' cannot be applied boolean and number
// tsconfig.json{"": {"": "commonjs","": true,"": true}}
預設開啟 --pretty 輸出
從 TypeScript 2.9 開始,如果輸出裝置支援彩色文字,錯誤資訊將預設在 pretty 模式下顯示。TypeScript 會檢查輸出流是否設定了 isTty 屬性。
在命令列中使用 --pretty false 或在 tsconfig.json 中設定 "pretty": false 來停用 pretty 輸出。
新的 --declarationMap
同時啟用 declarationMap 和 declaration 會使編譯器在輸出 .d.ts 檔案時同步生成 .d.ts.map 檔案。語言服務現在也可以理解這些對映檔案,並在可用時使用它們將基於宣告檔案的定義位置對映到其原始原始檔。
換句話說,對使用 declarationMap 生成的 .d.ts 檔案中的宣告進行“轉到定義”(go-to-definition)操作時,將帶您跳轉到該宣告定義的原始檔(.ts)位置,而不是 .d.ts 檔案。