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