【譯】TypeScript 3.5 變動內容

最近更新 VSCode 的時候,提示其內置的 Typescript 也到了 3.5.1 的版本,所以看了一下 TypeScript 3.5 的更新文檔,進行了簡單的翻譯,具體內容以下:git

速度優化

類型檢查

typescript 3.4 版本爲了修復一個 bug 致使了類型檢查變慢,構建時間大大增長、使用編輯器時有卡頓感。github

TypeScript 3.5 作了一些優化,目前在許多增量檢查中實際上會比 TypeScript 3.3 更快。typescript

--incremental構建

TypeScript 3.4 引入了一個新的 --incremental 編譯器選項。此選項將大量信息保存到.tsbuildinfo文件中,該文件可用於加速後續對 tsc 的調用。api

TypeScript 3.5 進行了一些優化,目前在幾百個項目的 --build 場景中,與TypeScript 3.4相比,重建的時間能夠減小68% !!瀏覽器

Omit 終於內置

lib.d.ts 如今內置 Omit 了,以下安全

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
複製代碼

所以,以前若是自行添加過全局的Omit 會致使報錯:Duplicate identifier 'Omit'.app

兩種解決方法:編輯器

  1. 刪除本身添加的Omit,只用lib.d.ts 提供的Omit
  2. 把本身添加的Omit 改成export 而非放到全局

優化 union 類型的多餘屬性檢查

TypeScript有一個特性,叫作對象文本中的多餘屬性檢查,其有助於避免 typoide

TypeScript 3.4 及以前,這一功能有一些問題,例以下面的例子不會報錯函數

type Point = {
    x: number;
    y: number;
};

type Label = {
    name: string;
};

const thing: Point | Label = {
    x: 0,
    y: 0,
    name: true // uh-oh!
};
複製代碼

在 TypeScript 3.5 中,類型檢查器至少驗證所提供的屬性屬於某個 union 成員,而且具備適當的類型,來保證上面的例子會報錯。

目前沒有發現這個優化會產生問題,但若是這項優化致使了代碼檢查不經過的問題,可使用這兩種方法解決:

  • 向對象添加類型斷言(e.g. { myProp: SomeType } as ExpectedType)
  • 向預期的類型添加索引簽名,以表示預期有未指定的屬性(e.g. interface ExpectedType { myProp: SomeType; [prop: string]: unknown })

--allowUmdGlobalAccess flag

allowUmdGlobalAccess標誌將容許從任何地方訪問 UMD 模塊中定義的全局,包括從 modules 中訪問。這多是不安全的,由於並非全部 UMD 模塊都在模塊系統中定義全局,可是在某些狀況下,您可能確實須要這種能力。例如,在瀏覽器環境中,一個UMD模塊須要在 require.js 以前加載。該模塊所以只能經過全局的方式來訪問。所以,若是你確實遇到了這種狀況,能夠用這個標誌來擺脫困境

更智能的 union 類型檢查

type S = { done: boolean, value: number }
type T =
    | { done: false, value: number }
    | { done: true, value: number };

declare let source: S;
declare let target: T;

target = source;

複製代碼

在TypeScript 3.5以前,上述檢查將會失敗,由於 S 既不能賦值給 { done: false, value: number } 也不能賦值給 { done: true, value: number }。這種設計有時會避免一些 bug:

interface Foo {
    kind: "foo";
    value: string;
}

interface Bar {
    kind: "bar";
    value: number;
}

function doSomething(x: Foo | Bar) {
    if (x.kind === "foo") {
        x.value.toLowerCase();
    }
}

// uh-oh - luckily TypeScript errors here!
doSomething({
    kind: "foo",
    value: 123,
});
複製代碼

但這種設計仍是比較詭異,在TypeScript 3.5中,當用T這樣的區別屬性分配類型時,語言會更進一步,將S這樣的類型分解爲每一個可能的類型的 union。在本例中,因爲booleantruefalse的聯合,因此S將被視爲{done: false, value: number} | {done: true, value: number}

泛型構造器的高階類型推斷

TypeScript 3.4 改進了「返回函數的泛型函數」的類型推斷:

function arrayify<T>(x: T): T[] {
    return [x];
}

type Box<U> = { value: U }
function boxify<U>(y: U): Box<U> {
    return { value: y };
}

let newFn = compose(arrayify, boxify);
複製代碼

在 TypeScript 3.4 以前的版本,newFN將是 (x: {}) => Box<{}[]>,在 TypeScript 3.4 以後,newFn 將是 <T>(x: T) => Box<T[]>

TypeScript 3.5 將這種行爲帶到了構造函數上,在某些 UI 庫(如React)中操做類組件的函數能夠更正確地操做泛型類組件:

type ComponentClass<P> = new (props: P) => Component<P>;
declare class Component<P> {
    props: P;
    constructor(props: P);
}

declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>;

type NestedProps<T> = { foo: number, stuff: T };

declare class GenericComponent<T> extends Component<NestedProps<T>> {
}

// type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>'
const GenericComponent2 = myHoc(GenericComponent);

複製代碼

函數的泛型參數將被隱式約束爲 unknown

之前泛型參數的隱式約束是空對象類型{}。在 TypeScript 3.5 中,沒有顯式約束的泛型類型參數如今隱式地約束爲unknown

{}unknown 有下述不一樣:

  • {} 能夠被相似這樣k["foo"] 訪問(雖然在 --noImplicitAny 下會報錯)
  • {}不能被賦值爲 nullundefined,但 unknown 能夠
  • {} 能夠被賦值給object,但 unknown 能夠

調用時,這意味着相似 T.toString() 等會報錯:

function foo<T>(x: T): [T, string] {
    return [x, x.toString()]
    // 會報錯:T 裏面沒有 toString 屬性
}
// 解決方法:增長顯式約束
function foo<T extends {}>(x: T): [T, string] {
    return [x, x.toString()]
}
複製代碼

對泛型參數的類型推斷失敗時,返回的類型一樣會從 {} 變成 unknown

function parse<T>(x: string): T {
    return JSON.parse(x);
}

// k has type 'unknown' - previously, it was '{}'.
const k = parse("...");

// 解決方法:
// 'k' now has type '{}'
const k = parse<{}>("...");
複製代碼

{ [k: string]: unknown } 再也不是任意對象類型的有效賦值目標

TypeScript中的索引簽名 { [s: string]: any }的行爲很特別:它是任何對象類型的有效賦值目標。這是一個特殊的規則,由於帶有索引簽名的類型一般不會產生這種行爲。

在以前 unknown 在這種狀況下的表現與 any 相同 ,{ [s: string]: unknown } 一樣是任意對象類型的有效賦值目標

let dict: { [s: string]: unknown };
// Was okay
dict = () => {};
複製代碼

通常來講,這個規則是有意義的——隱含的約束「它的全部屬性都是unknown的某個子類型」對於任何對象類型都是很是正確的。然而,在TypeScript 3.5中,{[s: string]: unknown} 這個特殊規則被刪掉了,緣由是上面的變動:

對泛型參數的類型推斷失敗時,返回的類型一樣會從 {} 變成 unknown

declare function someFunc(): void;
declare function fn<T>(arg: { [k: string]: T }): void;
fn(someFunc);
複製代碼

在TypeScript 3.4中,依次發生以下狀況:

  • T 找不到可選類型
  • 因此 T 被視爲{}
  • someFunc 不能賦值給 arg,由於沒有任何特殊規則,容許給 {[k: string]:{}} 隨意賦值
  • 報錯

因爲對泛型參數的類型推斷失敗時,其類型從 {} 變成 unknownarg的類型將變成{[k: string]: unknown},任何東西均可以賦值給它,所以調用將被錯誤地容許。因此 TypeScript 3.5 中,{ [k: string]: unknown }` 再也不是任意對象類型的有效賦值目標。

注意:普通的對象字面量不受影響:

const obj = { m: 10 }; 
// okay
const dict: { [s: string]: unknown } = obj;

複製代碼

以前的 { [s: string]: unknown },根據預期行爲,可使用幾種替代方法:

  • { [s: string]: any }
  • { [s: string]: {} }
  • object
  • unknown
  • any

修復了經過索引修改對象屬性時,類型檢查過於寬鬆的問題

TypeScript 3.4 下,如下邏輯不會報錯

type A = {
    s: string;
    n: number;
};

const a: A = { s: "", n: 0 };

function write<K extends keyof A>(arg: A, key: K, value: A[K]): void {
    // ???
    arg[key] = "hello, world";
}
// Breaks the object by putting a string where a number should be
write(a, "n", "oops");
複製代碼

在TypeScript 3.5中,這個邏輯被修復,上面的示例正確地發出了一個錯誤。

這種錯誤的大多數表示相關代碼中有潛在錯誤。若是確定沒錯,則能夠強行使用一發類型斷言。

在 ES5 環境下,Object.keys將拒絕原始值類型

ES5 環境下,若是調用 Object.keys 時傳入一個非對象參數,會拋出錯誤,但ES2015中,若是傳入的參數是原始類型, Object.keys 將返回 []

以前 TypeScript 沒注意到這種狀況,這會致使 ES5 環境下可能出問題,如今若是發現目標環境是 ES5,向 Object.keys 傳入原始類型,會報錯。

因此下述狀況下,有可能須要額外添加類型斷言:

function fn(arg: object | number, isArgActuallyObject: boolean) {
    if (isArgActuallyObject) {
        const k = Object.keys(arg as object);
    }
}
複製代碼

由於:

函數的泛型參數將被隱式約束爲 unknown

因此下面的調用也可能所以報錯

declare function fn<T>(): T;

// Was okay in TypeScript 3.4, errors in 3.5 under --target ES5
Object.keys(fn());
複製代碼

Editor 加強:Smart Select

TypeScript 3.5 讓 vscode 支持下圖的功能了:

Expand selection

Editor 加強:抽取匿名 type 爲具名 type

TypeScript 3.5 讓 vscode 支持下圖的功能了:

Extracting part of complex type to a type alias

展望將來

咱們預計3.6將帶來更好的創做和使用 generators 的體驗,支持ECMAScript的 private fields proposal,以及爲「支持快速增量構建和項目引用的構建工具」提供新 api。

自 3.6 起,更新頻率將從每 2 個月發一版變爲每 3 個月發一版。

Happy hacking!

– Daniel Rosenwasser and the TypeScript team

相關文章
相關標籤/搜索