TypeScript 2.9

keyof 功能加強

keyof 和映射類型支持 numbersymbol 類型的屬性名稱node

在索引類型和映射類型中,TypeScript 2.9 新增了對 numbersymbol 類型屬性名稱的支持。在以前的版本,keyof 和映射類型支持 string 類型的屬性名稱。git

新功能帶來的變化包括:github

  • 對於類型 Tkeyof T 的索引類型是 string | number | symbol 的子類型
  • 映射類型 { [P in K]: XXX } 會映射全部兼容 string | number | symbolK
  • 對於 for...in 語句中泛型爲 T 的對象,迭代變量的類型在以前推斷爲 keyof T,可是如今推斷爲 Extract<keyof T, string>(即,只包含 keyof T 中的類字符串值)

給定一個對象類型 Xkeyof X 的類型求值過程以下:json

  1. 若是 X 包含字符串索引簽名,那麼 keyof X 的值爲 stringnumber 和類符號屬性名的字面量類型組成的聯合類型,不然下一步
  2. 若是 X 包含數值索引簽名,那麼 keyof X 的值爲 number 和類字符串與類符號屬性名的字面量類型組成的聯合類型,不然下一步
  3. keyof X 的值爲類字符串、類數值和類符號屬性名的字面量類型組成的聯合類型

其中,瀏覽器

  • 對象的類字符串(string-like)屬性名包括標識符、字符串字面量和字符串字面量類型的計算屬性名
  • 對象的類數值(number-like)屬性名包括數值字面量和數值字面量類型的計算屬性名
  • 對象的類符號(symbol-like)屬性名包括符號類型的計算屬性名

在映射類型 { [P in K]: XXX } 中,K 中的每個字符串字面量類型都會引入一個字符串名稱的類型,每個數值類型字面量類型都會引入一個數值名稱的類型,每個符號類型都會引入一個符號名稱的類型。此外,若是 K 包含 string 類型,那麼會引入一個字符串索引簽名,若是包含 number 類型,也會引入一個數值索引簽名。模塊化

上面的文字可能不太好理解,來看看下面的例子:函數

const 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,標識符
    5: string;      // number-like, 數值字面量
    [c]: string;    // string-like, 字符串字面量類型的計算屬性
    [d]: string;    // number-like, 數值字面量類型的計算屬性
    [e]: string;    // symbol-like, 符號類型的計算屬性
    [E1.A]: string; // number-like, 數值枚舉類型的計算屬性
    [E2.A]: string; // string-like, 字符串枚舉類型的計算屬性
}

type K1 = keyof Foo; // "a" | 5 | "c" | 10 | typeof e | E1.A | E2.A;
type K2 = Extract<keyof Foo, string>; // "a" | "c" | E2.A
type K3 = Extract<keyof Foo, number>; // 5 | 10 | E1.A
type K4 = Extract<keyof Foo, symbol>; // typeof e

如今,因爲 keyof 能夠經過在鍵名類型中使用 number 來映射到數值索引簽名,因此諸如 Partial<T>Readonly<T> 的映射類型可以正確地處理對象類型中的數值索引簽名了。es5

type 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]; // x 推斷爲 string,以前版的版本會推斷爲 any

此外,得益於 keyof 操做支持使用 numbersymbol 命名的鍵值,當一個對象使用數值字面量(如數值枚舉類型)和惟一符號做爲屬性索引時,如今咱們能夠把訪問對象屬性的過程獨立地抽象出來了。命令行

const 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 sym3

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

let x1 = getValue(enumToStringMap, Enum.C);  // x1 類型爲 string ,返回 "Name C"
let x2 = getValue(symbolToNumberMap, sym3);  // x1 類型爲 number ,返回 3

這是一項重大功能變動。在以前的版本中,keyof 和映射類型只支持 string 類型命名的屬性。如今,若是還認爲 keyof T 的類型值老是 string 的話,那麼將會拋出錯誤,由於此時 keyof T 的類型值爲 string | number | symbol。舉例以下:debug

function useKey<T, K extends keyof T>(o: T, k: K) {
    var name: string = k;  // Error: keyof T is not assignable to string
}

針對該重大功能變動,有如下建議:

  • 若是函數只容許處理 string 類屬性名,那麼可使用 Extract<keyof T, string>:
function useKey<T, K extends Extract<keyof T, string>>(o: T, k: K) {
  var name: string = k;  // OK
}
  • 若是函能夠處理其餘類型的屬性鍵名,那麼可使用以下的作法:
function useKey<T, K extends keyof T>(o: T, k: K) {
  var name: string | number | symbol = k;
}
  • 不然,使用 --keyofStringsOnly 編譯選項來禁用新功能

JSX 泛型參數

如今,JSX 支持傳遞泛型參數給泛型組件了。

class GenericComponent<P> extends React.Component<P> {
    internalProp: P;
}

type Props = { a: number; b: string; };

const x = <GenericComponent<Props> a={10} b="hi"/>; // OK

const y = <GenericComponent<Props> a={10} b={20} />; // Error

標籤模板泛型參數

標籤模板是 ES2015 引入的一種新調用方式。跟調用表達式同樣,泛型函數也可用在標籤模板中,TypeScript 會根據類型參數進行類型推斷。

TypeScript 2.9 支持向標籤模板字符串傳遞泛型類型參數。

declare 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;

// a 的類型爲 string | number
let a = tag<string | number> `${100} ${"hello"}`;

import 類型

模塊能夠導入其餘模塊的類型聲明。可是,非模塊化的全局腳本沒法訪問模塊的類型聲明。import 爲此打開了新世界的大門。

經過在類型註釋中使用 import("mod"),咱們能夠訪問到該模塊導出的類型聲明,同時模塊代碼也並不會所以而引入。

來看一個簡單的例子。
假如模塊中有如下聲明:

// module.d.ts
export declare class Pet {
    name: string;
}

那麼就能夠在非模塊化的全局腳本中按如下方式使用它:

// global-script.ts
function adopt(p: import("./module").Pet) {
    console.log(`Adopting ${p.name}...`);
}

也能夠在 JSDoc 註釋中使用它:

// a.js
/**
 * @param p { import("./module").Pet }
 */
function walk(p) {
    console.log(`Walking ${p.name}...`);
}

消除聲明錯誤
支持 import 類型後,聲明文件生成過程當中拋出的許多錯誤能夠由編譯器直接處理,而不須要改變原輸入文件。

例如:

import { createHash } from "crypto";

export const hash = createHash("sha256");
//           ^^^^
// 導出變量 'hash' 已經或正在使用外部模塊 "crypto" 中的名稱 'Hash',但沒法對其命名

在 TypeScript 2.9 中,這樣的錯誤就不會拋出來了,而且生成的聲明文件以下:

export declare const hash: import("crypto").Hash;

支持 import.meta

TypeScript 2.9 引入了對 import.meta 的支持,它是 TC39 提案中的一種新的元屬性(meta-property)。

import.meta 的類型由全局類型 ImportMeta 所定義,位於 lib.es5.d.ts 。該接口的使用範圍是很受限的,主要用來爲 Node 或者瀏覽器添加衆所周知的屬性,以及可能根據上下文進行的全局加強。

例如,假設 __dirnameimport.meta 中老是可用的,那麼就能夠經過 ImportMeta 接口來新增該屬性:

// node.d.ts
interface ImportMeta {
    __dirname: string;
}

使用方法以下:

import.meta.__dirname // 類型爲 'string'

import.meta 只能在編譯輸出爲 ESNext 模塊和 ECMAScript 時使用。

編譯選項

--resolveJsonModule

在 Node.js 應用中,一般都會使用 .json 文件。在 TypeScript 2.9 中,--resolveJsonModule 編譯選項可容許從 .json 中導入、導出其類型。

// settings.json

{
    "repo": "TypeScript",
    "dry": false,
    "debug": false
}
import settings from "./settings.json";

settings.debug === true;  // OK
settings.dry === 2;  // Error: '===' 不能用於比較 boolean 和 number 類型
// tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "resolveJsonModule": true,
        "esModuleInterop": true
    }
}

--pretty

自 TypeScript 2.9 開始,若是輸出設備支持多顏色文本,錯誤信息將默認開啓 --pretty 選項。TypeScript 會自動檢查輸出流是否設置 isTty 屬性。

能夠在命令行使用 --pretty false 或者 tsconfig.json 中設置 "pretty": false 來禁用 --pretty 輸出。

--declarationMap

若是在開啓 --declaration 的狀況下,同時開啓 --declarationMap ,那麼編譯器會同時生成 .d.ts.d.ts.map 文件。語言服務如今可以正確識別這些映射文件,而且使用它們來映射到源碼。

也就是說,在使用「跳到定義之處」功能時,會直接跳轉到源碼文件,而不是 .d.ts 文件。

相關文章
相關標籤/搜索