TypeScript 3.6正式發佈!新增4項突破性特點

8 月 28 日微軟正式發佈了 TypeScript 3.6 版,本文將主要介紹 TypeScript 3.6 版中的更新內容,包括:語言和編譯器、新的TypeScript Playground、編輯功能等。​

我們很高興地宣佈,TypeScript 3.6正式面世了!

有些人可能還不太瞭解TypeScript,這裏做一個簡單介紹:TypeScript是一種基於JavaScript的語言,在後者的基礎上添加了可選的靜態類型;TypeScript編譯器可以檢查這些類型以捕獲程序中的常見錯誤(例如屬性拼寫錯誤和函數調用錯誤等);然後可以使用TypeScript編譯器和Babel等工具將基於最前沿規範編寫的TypeScript代碼轉換爲符合標準的ECMAScript代碼,後者可在任何瀏覽器或運行時(甚至是隻支持ES3或ES5的舊版本)上運行。

TypeScript不僅具備類型檢查和較新的ECMAScript功能,其編輯工具也是業界一流水平,是TypeScript項目不可或缺的一部分;多種類型的編輯器爲TypeScript提供了代碼自動完成、重構和快速修復等功能。如果你在Visual Studio或Visual Studio Code中編輯過JavaScript文件,其實這些體驗是由TypeScript提供的,所以你可能已經在不知情的情況下開始使用TypeScript了!

你可以查看TypeScript官方網站瞭解更多信息。TypeScript可以通過NuGet獲取,或在npm中鍵入以下命令:

npm install -g typescript

可選的編輯器有:

不久的將來我們還會提供其他編輯器的支持

下面來看看3.6版中的更新內容吧!

語言和編譯器

更嚴格的生成器

TypeScript 3.6對迭代器和生成器函數的檢查更嚴格了。在早期版本中,用戶使用生成器時無法判斷一個值是從生成器yield還是返回的。

function* foo() {
    if (Math.random() < 0.5) yield 100;
    return "Finished!"
}

let iter = foo();
let curr = iter.next();
if (curr.done) {
    // TypeScript 3.5及之前版本把它當成 'string | number'.
    // 它應該知道這是 'string' ,因爲 'done' 是 'true'!
    curr.value
}

此外,生成器之前會假定yield的類型總是any。

function* bar() {
    let x: { hello(): void } = yield;
    x.hello();
}

let iter = bar();
iter.next();
iter.next(123); // oops! runtime error!

TypeScript 3.6中的檢查器現在知道第一段代碼中的curr.value的正確類型應該是string,並且在第二段代碼中調用next()時會正確地報錯。這是因爲Iterator和IteratorResult類型聲明中現在引入了一些新的類型參數,且新版TypeScript會用Generator這個新類型來表示生成器。

Iterator類型現在允許用戶指定yield類型、返回的類型以及next可以接受的類型。

interface Iterator<T, TReturn = any, TNext = undefined> {
    // 接收 0 或 1 參數 - 不接收 'undefined'
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return?(value?: TReturn): IteratorResult<T, TReturn>;
    throw?(e?: any): IteratorResult<T, TReturn>;
}

在此基礎上,新的Generator類型是一個Iterator,它總是同時存在return和throw方法,並且也是可迭代的。

interface Generator<T = unknown, TReturn = any, TNext = unknown>
        extends Iterator<T, TReturn, TNext> {
    next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
    return(value: TReturn): IteratorResult<T, TReturn>;
    throw(e: any): IteratorResult<T, TReturn>;
    Symbol.iterator: Generator<T, TReturn, TNext>;
}

爲了區分返回值和生成值,TypeScript 3.6將IteratorResult類型轉換爲差別聯合類型:

type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;

interface IteratorYieldResult<TYield> {
    done?: false;
    value: TYield;
}

interface IteratorReturnResult<TReturn> {
    done: true;
    value: TReturn;
}

簡而言之,這意味着你在直接處理迭代器時能夠適當地縮小迭代器的值。

爲了正確表示可以從調用next()傳遞給生成器的類型,TypeScript 3.6還可以在生成器函數主體內推斷出某些yield用法。

function* foo() {
    let x: string = yield;
    console.log(x.toUpperCase());
}

let x = foo();
x.next(); // 對 'next' 的第一個調用都會被忽略
x.next(42); // error! 'number' 無法被分配給 'string'

如果你更喜歡顯式方法,那麼還可以強制可以用yield表達式執行返回、yield和計算的值的類型使用顯式返回類型。下面的例子中,next( )只能用布爾值調用,並且根據done的值,value可以是string或number。

/**
 * - yields numbers
 * - returns strings
 * - can be passed in booleans
 */
function* counter(): Generator<number, string, boolean> {
    let i = 0;
    while (true) {
        if (yield i++) {
            break;
        }
    }
    return "done!";
}

var iter = counter();
var curr = iter.next()
while (!curr.done) {
    console.log(curr.value);
    curr = iter.next(curr.value === 5)
}
console.log(curr.value.toUpperCase());

// prints:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!

這部分更改的詳情可參閱github

更準確的數組擴展

在目標爲ES2015之前的版本中,諸如for/of循環和數組擴展之類的結構用的發射有些複雜。因此TypeScript默認使用更簡單的發射,只支持數組類型,並支持使用–downlevelIteration標誌在其他類型上迭代。在此標誌下發出的代碼更準確,但體積也大得多。

默認情況下–downlevelIteration是關閉的,因爲以ES5爲目標的用戶多數只使用帶數組的迭代結構。但某些邊緣情況下只支持數組的發射還是有一些可見的差異。

例如,以下示例

[...Array(5)]

等同於下列數組:

[undefined, undefined, undefined, undefined, undefined]

但是,TypeScript會將原始代碼轉換爲下面的代碼:

Array(5).slice();

它們是有些不一樣的。Array(5)生成一個長度爲5的數組,但沒有定義的屬性槽!

1 in [undefined, undefined, undefined] // true
1 in Array(3) // false

當TypeScript調用slice()時,它還會創建一個其索引尚未設置的數組。

這可能不太好理解,但其實有許多用戶遇到了這種麻煩。TypeScript 3.6不再使用slice()和內置函數,而是引入了一個新的__spreadArrays助手,將ECMAScript 2015的內容在較舊的目標中模擬出來,不用 --downlevelIteration。 __spreadArrays也可以在tslib中使用(如果你想縮小包的體積,那麼非常值得一試)。

有關更多信息,請參閱資料

改進了Promise相關的用戶體驗

Promise是當今最常用的異步數據方法之一。然而面向Promise的API通常會讓用戶感到困惑。TypeScript 3.6引入了一些改進,避免用戶錯誤處理Promise。

例如,在將Promise的內容傳遞給另一個函數之前,人們往往會忘記.then()或await這部分內容。TypeScript現在有了對應的錯誤消息,並告知用戶他們可能應該使用await關鍵字。

interface User {
    name: string;
    age: number;
    location: string;
}

declare function getUserData(): Promise<User>;
declare function displayUser(user: User): void;

async function f() {
    displayUser(getUserData());
//              ~~~~~~~~~~~~~
// 類型 'Promise<User>' 的參數不能分配給類型 'User'的參數.
//   ...
// Did you forget to use 'await'?
}

在await或.then()一個Promise之前就嘗試訪問方法也是很常見的錯誤。這類錯誤很多,我們都做了改進。

async function getCuteAnimals() {
    fetch("https://reddit.com/r/aww.json")
        .json()
    //   ~~~~
    // 屬性 'json' 在類型 'Promise<Response>'上不存在.
    //
    // Did you forget to use 'await'?
}

這裏的目的是就算用戶沒意識到要await,起碼這些消息能給出一些提示。

除了改進Promise相關的錯誤消息外,我們現在還在某些情況下提供了快速修復。

有關更多詳細信息,請參閱原始問題及相關鏈接。

對標識符的Unicode支持改進

當發射到ES2015及更高版本的目標時,TypeScript 3.6對標識符的Unicode字符提供了更好的支持。

const 𝓱𝓮𝓵𝓵𝓸 = "world"; // 以前不允許, 現在對 '--target es2015' 允許

SystemJS中的import.meta支持

當module目標設置爲system時,TypeScript 3.6支持將import.meta轉換爲context.meta。

// 這個模塊:

console.log(import.meta.url)

// 被改成:

System.register([], function (exports, context) {
  return {
    setters: [],
    execute: function () {
      console.log(context.meta.url);
    }
  };
});

在環境上下文中允許get和set訪問器

以前的TypeScript版本不允許在環境上下文中get和set訪問器(例如在declare-d類中,或者在.d.ts文件中)。理由是對於這些屬性的讀寫而言訪問器與屬性沒有區別;但是因爲ECMAScript的類字段提案可能與現有TypeScript版本中的行爲不同,我們意識到我們需要一種方法來對接這種行爲差異,以便在子類中提供適當的錯誤。

因此,用戶現在可以在TypeScript 3.6的環境上下文中編寫getter和setter。

declare class Foo {
    // 在 3.6+ 版本中允許.
    get x(): number;
    set x(val: number): void;
}

在TypeScript 3.7中編譯器也將利用此功能,以便生成的.d.ts文件也發射get/set訪問器。

環境類和函數可以合併

在以前版本的TypeScript中,任何情況下合併類和函數都是錯誤的。現在環境類和函數(具有declare修飾符或在.d.ts文件中的類/函數)可以合併了。這意味着現在你可以編寫以下代碼:

export declare function Point2D(x: number, y: number): Point2D;
export declare class Point2D {
    x: number;
    y: number;
    constructor(x: number, y: number);
}

這樣就用不着再寫成:

export interface Point2D {
    x: number;
    y: number;
}
export declare var Point2D: {
    (x: number, y: number): Point2D;
    new (x: number, y: number): Point2D;
}

這樣做的一個優點是可以很容易地表達可調用的構造函數模式,同時還允許名稱空間與這些聲明合併(因爲var聲明不能與namespace合併)。

在TypeScript 3.7中,編譯器也將利用此功能,以便從.js文件生成的.d.ts文件可以正確捕獲類函數的可調用性和可構造性。

更多詳細信息請參閱GitHub上的原始PR

爲–build和–incremental提供API支持

TypeScript 3.0開始支持引用其他項目,並使用–build標誌以增量方式構建它們。之後TypeScript 3.4引入了–incremental標誌來保存之前編譯的相關信息,這樣就可以只重建特定文件了。這些標誌可以幫助用戶更快、靈活地構建項目。可惜這些標誌無法用於Gulp和Webpack這樣的第三方構建工具。TypeScript 3.6現在公開了兩組API來處理項目引用和增量程序構建。

創建–incremental構建時用戶可以利用createIncrementalProgram和createIncrementalCompilerHost API。用戶還可以使用新公開的readBuilderProgram函數從這個API生成的.tsbuildinfo文件中重新保存舊程序實例,該函數僅用於創建新程序(你無法修改返回的實例,它僅用於其他create*Program函數中的oldProgram參數)。

針對項目引用方面,新版引入了一個新的createSolutionBuilder函數,它返回一個新類型SolutionBuilder的實例。

有關這些API的詳細信息可參閱資料

新的TypeScript Playground

應用戶呼籲,新版TypeScript Playground做了大幅改進,引入了許多全新功能。新版Playground基本上是社區流行的Artem TyurinTypeScript Playground的一個fork。我們在這裏非常感謝Artem提供的幫助!

新版Playground提供了許多新選項,包括:

  • target選項(允許用戶從es5切換到es3、es2015、esnext等)
  • 所有嚴格標誌(包括strict)
  • 支持純JavaScript文件(使用allowJS和可選的checkJs)

這些選項在共享Playground樣本時也能使用,讓用戶可以更可靠地共享示例,無需告訴收件人“哦,別忘了打開noImplicitAny選項!”。

在不久的將來,我們將改進Playground樣本功能、添加JSX支持、改進自動類型獲取等,讓用戶使用Playground時如同在使用自己的編輯器一樣。

我們歡迎大家在GitHub上提交反饋和請求!

編輯功能

分號感知代碼編輯

Visual Studio和Visual Studio Code等編輯器可以自動應用快速修復、重構、自動從其他模塊導入值等轉換。這些轉換由TypeScript提供支持,而舊版本的TypeScript會無條件地在每個語句的末尾添加分號;但很多用戶不喜歡這種風格,不希望編輯器自動插入分號。

TypeScript現在變得非常聰明,可以在應用這些編輯時檢測你的文件是否使用分號。如果你的文件不怎麼用分號的話TypeScript也不會添加分號。

更多詳細信息請參閱資料

更智能的自動導入

JavaScript有許多不同的模塊語法或約定:ECMAScript標準是一種、Node支持的一種(CommonJS)、AMD一種、System.js又是一種,還有更多!在大多數情況下TypeScript默認使用ECMAScript模塊語法來自動導入,遇到有些使用不同編譯器設置的TypeScript項目就不怎麼合適,在使用純JavaScript和require調用的Node項目中也不搭配。

TypeScript 3.6變得更聰明瞭一些,可以先查看你現有的導入後再決定怎樣自動導入其他模塊。你可以在此處查看更多信息

重大更新

命名爲“constructor”的類成員現在是構造函數

根據ECMAScript規範,使用名爲constructor的方法的類聲明現在是構造函數,無論它們是使用標識符名稱還是字符串名稱聲明都是如此。

class C {
    "constructor"() {
        console.log("我是構造函數.");
    }
}

注意有一個例外,就是計算屬性的名稱等於“constructor”的情況。

class D {
    "constructor" {
        console.log("我不是一個構造函數 - 只是一個方法");
    }
}

DOM更新

新版lib.dom.d.ts中的許多聲明已被刪除或更改。具體包括(但不限於)以下內容:

  • 全局window不再定義爲類型Window——而是將其定義爲類型Window&typeof globalThis。在某些情況下,最好將其類型稱爲typeofwindow。
  • GlobalFetch已經移除了,取而代之的是WindowOrWorkerGlobalScope。
  • Navigator上的某些非標準屬性消失了。
  • experimental-webgl上下文移除了。替代品是webgl或webgl2。

JSDoc註釋不再合併

在JavaScript文件中,TypeScript只會在JSDoc註釋之前確定聲明的類型。

/**
 * @param {string} arg
 */
/**
 * oh, hi, were you trying to type something?
 */
function whoWritesFunctionsLikeThis(arg) {
    // 'arg' has type 'any'
}

關鍵字不能包含轉義序列

以前關鍵字允許包含轉義序列。TypeScript 3.6中就不行了。

while (true) {
    \u0063ontinue;
//  ~~~~~~~~~~~~~
//  error! Keywords cannot contain escape characters.
}

未來計劃

想要了解官方團隊未來要開展的工作,請查看今年7月至12月的半年路線圖計劃

我們希望這個新版本能繼續改善你的開發體驗。你有任何建議或遇到任何問題我們都很感興趣,歡迎大家在GitHub上提交反饋。

英文原文:https://devblogs.microsoft.com/typescript/announcing-typescript-3-6/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章