Typescript 5 來了

語言: CN / TW / HK

## 背景

前段時間發佈了 Typescript 5.0 beta 版,預計3月14號發佈正式版,我們來一起看看有哪些新特性。

## 裝飾器

TS5 支持的是 Stage3 裝飾器,進入到 Stage3 的特性基本上就可以認為可以加入 JS 標準了,更多內容可以看下之前我整理的文檔:再來了解一下裝飾器

## const 類型參數

Typescript 通常會把一個對象推斷成一個更通用的類型。比如下面的例子,推斷出的 names 是 string[];

```JavaScript type HasNames = { names: readonly string[] }; function getNamesExactly(arg: T): T["names"] { return arg.names; }

// 推斷的類型是: string[] const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]}); ```

假如我們希望推斷的類型是 ["Alice", "Bob", "Eve"],就需要用 as const 轉化一下;

```JavaScript

// 推斷的類型是 ["Alice", "Bob", "Eve"] const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const); ```

這種方式用起來不夠優雅而且可能會忘記加,所以 TS5 的類型參數支持了 const 描述符;

```JavaScript type HasNames = { names: readonly string[] }; function getNamesExactly(arg: T): T["names"] { return arg.names; }

// 推斷類型: readonly ["Alice", "Bob", "Eve"] // 這樣就不需要 as const 了 const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] }); ```

不過需要注意的是,如果約束是可變類型,會存在一些問題;

什麼是可變類型?舉個例子:Array vs ReadonlyArray,前者是可變類型,後者是不可變類型

```JavaScript declare function fnBad(args: T): void;

// T 仍然是 string[],因為 readonly ["a", "b", "c"] 不能賦值給 string[] fnBad(["a", "b" ,"c"]); ```

本來推斷出的 T 應該是 readonly ["a", "b", "c"],但是 readonly ["a", "b", "c"] 不能賦值給 string[],所以 T 的類型回退成了 string[]

解決的辦法,也很簡單,使用 readonly string[] 替換 string[]

```JavaScript declare function fnGood(args: T): void;

// T 是 readonly ["a", "b", "c"] fnGood(["a", "b" ,"c"]); ```

還有一點需要注意,const 修飾符只能影響直接寫在函數調用中的對象、數組和原始表達式的推斷,舉個例子:

```JavaScript declare function fnGood(args: T): void;

const arr = ["a", "b" ,"c"]; // T 仍然是 string[] -- const 修飾符在這裏沒有任何效果 fnGood(arr); // T 是 readonly string ["a", "b" ,"c"] fnGood(["a", "b" ,"c"]); ```

## 所有枚舉都是 union 枚舉

TS 最初設計的枚舉類型比較簡單,除了 E.Foo and E.Bar 只能賦值給 E 類型的變量之外,這些枚舉類型的成員其實就是數字。

JavaScript enum E { Foo = 10, Bar = 20, }

TS2.0 引入了枚舉字面量,它給每個成員都分配了一個類型,那麼這個枚舉類型就變成了由所有成員類型組成的 union 類型,我們把它叫做 union enum

JavaScript // Color 就類似於一個 Union類型:Red | Orange | Yellow | Green | Blue | Violet enum Color { Red, Orange, Yellow, Green, Blue, Violet }

union enum 的優勢在於,我們可以使用這個枚舉類型的子集。如下,Color 包含六個成員,我們可以定義包含三個成員的子集類型。

```JavaScript enum Color { Red, Orange, Yellow, Green, Blue, Violet }

// 每個枚舉成員都有自己的類型 // 定義一個只包含三個成員的 Union 類型,相當於 type PrimaryColor = Color.Red | Color.Green | Color.Blue; ```

但是枚舉成員的類型與枚舉成員的值是強相關的。如果枚舉成員的值是函數,就無法計算出成員的值,所以就沒辦法給每個成員分配對應的類型,也就沒辦法將枚舉轉變成 union enum

TS5 解決了這個問題,即使成員的值是函數,也能為其創建唯一的類型;每個枚舉類型都是 union enum

![image](http://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8bebb8a217fc4c1c8cda98ba895de1f9~tplv-k3u1fbpfcp-watermark.image?) ![image](http://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e1108c0d06394620bf3daa1b65f22c31~tplv-k3u1fbpfcp-watermark.image?)

## 枚舉類型的兩個新報錯

  1. 給枚舉變量賦值成員值以外的值時會報錯

```JavaScript enum SomeEvenDigit { Zero = 0, Two = 2, Four = 4 }

// 錯誤,1 不是成員的值 let m: SomeEvenDigit = 1; ```

  1. 成員值是 string/number 混合並且存在間接賦值的場景

```JavaScript enum Letters { A = "a" } enum Numbers { one = 1, two = Letters.A }

// 錯誤 const t: number = Numbers.two; ```

## 支持 export type *

TS3.8 支持了針對 type 的導入,TS5 在此基礎上擴展出了 export * from "module" 或者 export * as ns from "module"

```TypeScript // models/vehicles.ts export class Spaceship { // ... }

// models/index.ts export type * as vehicles from "./spaceship";

// main.ts import { vehicles } from "./models";

function takeASpaceship(s: vehicles.Spaceship) { // 這裏沒問題 - vehicles 只能當成類型使用 }

function makeASpaceship() { return new vehicles.Spaceship(); // ^^^^^^^^ // vehicles 不能當成值使用,因為它是通過 export type 導出的. } ```

## JSDoc 支持 @satisfies

TS4.9 引入了 satisfies 操作符,它可以保證變量兼容某個類型,比如

```TypeScript interface NewType { name: string; hobby: string | string[] }

/ * a 被推斷為 * { * name: string; * hobby: string; . } / let a: NewType = { name: 'name1', hobby: 'one hobby' };

(hobby as string).toLower ```

TS5 讓 JSDoc 上也支持了 satisfies

```TypeScript // @ts-check

/* * @typedef NewType * @prop {string} [name] * @prop {string | string[]} [hobby] /

/* * @satisfies {NewType} / let a = { name: 'name1', hobby: 'one hobby' };

```

## JSDoc 支持 @overload

JSDoc 通過 @overload 支持函數重載。

TS

```TypeScript // 函數重載: function printValue(str: string): void; function printValue(num: number, maxFractionDigits?: number): void;

// 函數定義: function printValue(value: string | number, maximumFractionDigits?: number) { if (typeof value === "number") { const formatter = Intl.NumberFormat("en-US", { maximumFractionDigits, }); value = formatter.format(value); }

console.log(value);

} ```

JSDoc

```TypeScript // @ts-check

/* * @overload * @param {string} value * @return {void} /

/* * @overload * @param {number} value * @param {number} [maximumFractionDigits] * @return {void} /

/* * @param {string | number} value * @param {number} [maximumFractionDigits] / function printValue(value, maximumFractionDigits) { if (typeof value === "number") { const formatter = Intl.NumberFormat("en-US", { maximumFractionDigits, }); value = formatter.format(value); }

console.log(value);

} ```

## extends 支持多個配置文件

當維護多個項目時,通常每個項目的 tsconfig.json 都會繼承於一份基準配置。為了提高 extends 的靈活性,TS5 支持集成多個配置文件。

```JavaScript // tsconfig1.json { "compilerOptions": { "strictNullChecks": true } }

// tsconfig2.json { "compilerOptions": { "noImplicitAny": true } }

// tsconfig.json { "extends": ["./tsconfig1.json", "./tsconfig2.json"], "compilerOptions": { },
"files": ["./index.ts"] } ```

## customConditions

假設有一個第三方包的 package.json 包含如下代碼:

TypeScript { // ... "exports": { "my-condition": "./foo.mjs", "node": "./bar.mjs", // 用於 node 環境 "import": "./baz.mjs", // 通過 import/import() 引入時使用 "require": "./biz.mjs" // 通過 require 引入時使用 } }

並且你項目中的 tsconfig.json 是這樣

TypeScript { "compilerOptions": { "target": "es2022", "moduleResolution": "bundler", "customConditions": ["my-condition"] } }

此時,如果在你項目中 import 了這個第三方包,實際導入的是這個入口 foo.mjs。

## 關係型運算符禁止隱式類型轉換

TS5 之前已經禁止了算數運算符的隱式轉換,下面的代碼會有報錯

JavaScript // TS5 之前和之後都會報錯 function func(ns: number | string) { return ns * 4; // 錯誤, 可能存在隱式轉換 }

TS5 新增了禁止關係運算符中的隱式類型轉換

```JavaScript function func(ns: number | string) { return ns > 4; // 錯誤, 可能存在隱式轉換 }

// 需要做一次顯示類型轉換 function func(ns: number | string) { return +ns > 4; // 正確 } ```

## 其他
  1. switch/case 自動補全 value 的所有未覆蓋的字面量類型;
![image](http://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/22c66aedeb6c4f548d51f7206b97e136~tplv-k3u1fbpfcp-watermark.image?)
  1. 針對 --build 可以指定特定產物的標誌,比如:打包產物需要包含類型聲明文件tsc --build -p ./my-project-dir --declaration

  2. --verbatimModuleSyntax 簡化在編譯產物裏對於 import 的剔除策略

```TypeScript

// 在編譯產物裏,整體剔除 import type { A } from "a";

// 在編譯產物裏改寫成 'import { b } from "bcd";' import { b, type c, type d } from "bcd";

// 在編譯產物裏改寫成 'import {} from "xyz";' import { type xyz } from "xyz"; ```

  1. --moduleResolution為了支持更多打包場景,在 node16/nodenext 基礎上,新增了 bundler;

  2. 編譯速度、打包體積都有很明顯的優化。

## 如何體驗 TS5?

第一步:通過 npm 安裝 ts beta:

TypeScript npm install typescript@beta

第二步:安裝 VSCode 擴展:JavaScript and TypeScript Nightly - Visual Studio Marketplace

第三步:在 VSCode 中選擇 TS 版本(Command + Shift + P

![image](http://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/081e305e40d54543b0f51f18c13932f0~tplv-k3u1fbpfcp-watermark.image?)

## 參考

announcing-typescript-5-0-beta

satisfies:dev.to