Typescript 5 來了
## 背景
前段時間釋出了 Typescript 5.0 beta 版,預計3月14號釋出正式版,我們來一起看看有哪些新特性。
## 裝飾器
TS5 支援的是 Stage3 裝飾器,進入到 Stage3 的特性基本上就可以認為可以加入 JS 標準了,更多內容可以看下之前我整理的文件:再來了解一下裝飾器
## const 型別引數
Typescript 通常會把一個物件推斷成一個更通用的型別。比如下面的例子,推斷出的 names 是 string[];
```JavaScript
type HasNames = { names: readonly string[] };
function getNamesExactly
// 推斷的型別是: 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
// 推斷型別: readonly ["Alice", "Bob", "Eve"] // 這樣就不需要 as const 了 const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] }); ```
不過需要注意的是,如果約束是可變型別,會存在一些問題;
什麼是可變型別?舉個例子:Array vs ReadonlyArray,前者是可變型別,後者是不可變型別
```JavaScript
declare function fnBad
// 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
// T 是 readonly ["a", "b", "c"] fnGood(["a", "b" ,"c"]); ```
還有一點需要注意,const 修飾符只能影響直接寫在函式呼叫中的物件、陣列和原始表示式的推斷,舉個例子:
```JavaScript
declare function fnGood
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。
## 列舉型別的兩個新報錯
- 給列舉變數賦值成員值以外的值時會報錯
```JavaScript enum SomeEvenDigit { Zero = 0, Two = 2, Four = 4 }
// 錯誤,1 不是成員的值 let m: SomeEvenDigit = 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; // 正確 } ```
- switch/case 自動補全 value 的所有未覆蓋的字面量型別;
-
針對 --build 可以指定特定產物的標誌,比如:打包產物需要包含型別宣告檔案tsc --build -p ./my-project-dir --declaration
-
--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"; ```
-
--moduleResolution為了支援更多打包場景,在 node16/nodenext 基礎上,新增了 bundler;
-
編譯速度、打包體積都有很明顯的優化。
第一步:通過 npm 安裝 ts beta:
TypeScript
npm install typescript@beta
第二步:安裝 VSCode 擴充套件:JavaScript and TypeScript Nightly - Visual Studio Marketplace
第三步:在 VSCode 中選擇 TS 版本(Command + Shift + P)
## 參考
announcing-typescript-5-0-beta
satisfies:dev.to