首發(fā)知乎:https://zhuanlan.zhihu.com/p/473952171
今天,我們發(fā)布了 TypeScript 4.6.
原文鏈接:https://devblogs.microsoft.com/typescript/announcing-typescript-4-6/
作者:Daniel
原文日期:2022.02.28
全文:10001 字。閱讀時間 30 分鐘溉知。
今天茵瘾,我們發(fā)布了 TypeScript 4.6.
如果你還不熟悉 TypeScript,TypeScript 是在 JavaScript 之上構建的一個編程語言,并且為 JavaScript 提供了類型的語法褐缠。類型幫助你知道你的代碼的變量和函數(shù)的種類脱货。TypeScript 可以利用這些信息岛都,幫助你消除拼寫錯誤,或者是不小心忘記的 null 和 undefined 的檢查振峻。但是 TypeScript 提供的遠比這些多臼疫,TypeScript 可以用這些信息極大的提升你的開發(fā)體驗,提供例如代碼補全铺韧,跳轉定義多矮,重命名等功能。如果你已經(jīng)用 Visual Studio 或者 Visual Studio Code 進行編寫 JavaScript 的項目哈打,你其實已經(jīng)間接使用了 TypeScript塔逃!
開始使用 TypeScript,你可以通過 NuGet料仗,或者通過下面這個命令:
npm install typescript
你可以通過編輯器支持:
- 下載 Visual Studio 2022/2019
- 安裝 Visual Studio Code 或者根據(jù)文檔去使用更新版本的 TypeScript
- 在 Sublime Text 3 里使用包管理工具
如果你已經(jīng)讀了我們Beta 版本或者 RC 版本的博文湾盗,你可以直接看本次發(fā)布的變化部分。
下面是 TypeScript 4.6 新增的部分:
- 允許 class 的構造函數(shù)內 super() 前執(zhí)行代碼
- 對于解構 Discriminated 聯(lián)合類型的控制流分析引擎(Control Flow Analysis)增強
- 增強遞歸深度檢查
- 索引訪問推斷增強
- Dependent 參數(shù)的控制流分析引擎增強
- -target es2022
- 移除 react-jsx 不必要的參數(shù)
- JSDoc 名字建議
- 對于 JavaScript 提供更多的語法和綁定錯誤提示
- TypeScript Trace 分析工具
- 重大改變
對于 Beta 和 RC 版本以來的變化
當我們宣布 beta立轧,我們沒有提兩個重要的功能:對于 Destructured Discriminated Unions 的控制流分析引擎增強和增加 --target es2022格粪。從 beta 版本以來,還有一個值得注意的變化是:我們移除了react-jsx 的 void 0參數(shù)氛改。
距離我們 RC 版本的變化主要是帐萎,對于不匹配 JSDoc 參數(shù)名的提示。
距離 RC 版本胜卤,我們還修復了一些 issues疆导,修復了一些奇怪的報錯信息,增強了某些場景大約 3% 的類型檢查的速度葛躏。你可以在這里讀到更詳細的情況澈段。
允許 class 的構造函數(shù)內 super() 前執(zhí)行代碼
在 JavaScript 中悠菜,你必須在調用 this 前強制執(zhí)行 super()。TypeScript 也強制執(zhí)行了這個約定败富,但是我們實現(xiàn)這個限制用了過于嚴格的限制悔醋。在之前的版本,如果在 super() 前調用任何代碼都會報錯:
class Base {
// ...
}
class Derived extends Base {
someProperty = true
constructor() {
// 報錯!
// have to call 'super()' first because it needs to initialize 'someProperty'.
doSomeStuff()
super()
}
}
這對于檢查調用 this 前必須調用 super()變得很容易兽叮,但是這樣做芬骄,你連不用 this 的代碼,也不可以寫了鹦聪。TypeScript 4.6 現(xiàn)在允許你可以在 super() 前寫不含有 this 的代碼德玫。
感謝 Joshua Goldberg 的 PR。
解構 Discriminated 聯(lián)合類型的控制流分析引擎增強
TypeScript 可以收束名為 discriminant 屬性的類型椎麦。例如宰僧,在下面的代碼片段,TypeScript 可以通過 kind 的值收束 action 的類型观挎。
type Action =
| { kind: 'NumberContents'; payload: number }
| { kind: 'StringContents'; payload: string }
function processAction(action: Action) {
if (action.kind === 'NumberContents') {
// `action.payload` 是 number 類型.
let num = action.payload * 2
// ...
} else if (action.kind === 'StringContents') {
// `action.payload` 是 string 類型.
const str = action.payload.trim()
// ...
}
}
這可以讓我們用同一個 objects 存儲不同類型的數(shù)據(jù)琴儿,但是需要手動添加一個字段,告訴 TypeScript嘁捷,這個數(shù)據(jù)是什么造成。
這在使用 TypeScript 非常常見。但是雄嚣,也許晒屎,你想更進一步,做下面這個例子缓升,在條件判斷前鼓鲁,提前對數(shù)據(jù)進行解構:
type Action =
| { kind: 'NumberContents'; payload: number }
| { kind: 'StringContents'; payload: string }
function processAction(action: Action) {
const { kind, payload } = action
if (kind === 'NumberContents') {
let num = payload * 2
// ...
} else if (kind === 'StringContents') {
const str = payload.trim()
// ...
}
}
在之前的版本, TypeScript 會直接報錯港谊,一旦 kind 和 payload 進行解構骇吭,他們會認為是原有類型并集的獨立的變量。
但是歧寺,在 TypeScript 4.6燥狰,這個可以工作了。
當使用 const 進行解構斜筐,或者解構以后龙致,沒有進行過重新賦值的情況下,TypeScript 可以記住從 discriminated 聯(lián)合類型里解構的類型顷链。在合適的情況下目代,解構出來的類型的關聯(lián)依然保持,所以在上面的例子里,對于 kind 的收束可以獲得對應的 payload 的類型像啼。
對于更詳細的信息,可以查看這個 PR潭苞。
增強遞歸深度檢查
TypeScript 因為基于一個結構類型系統(tǒng)忽冻,并且還要提供范型,所以遇到很多有趣的挑戰(zhàn)此疹。
在一個結構類型系統(tǒng)中僧诚,object 類型可以通過他們有的成員的類似是否匹配來判斷是否兼容。
interface Source {
prop: string
}
interface Target {
prop: number
}
function check(source: Source, target: Target) {
target = source
// 報錯!
// Type 'Source' is not assignable to type 'Target'.
// Types of property 'prop' are incompatible.
// Type 'string' is not assignable to type 'number'.
}
注意到蝗碎, Source 和 Target 是否可以兼容湖笨,要看他們的成員是否可以賦值。在這個例子里蹦骑,就是看 prop 的類型慈省。
當引入范型時,這個問題變得困難了眠菇。例如边败,對于 Source<string> 是否可以賦值給 Target<number>?</number></string>
interface Source<T> {
prop: Source<Source<T>>
}
interface Target<T> {
prop: Target<Target<T>>
}
function check(source: Source<string>, target: Target<number>) {
target = source
}
為了知道這個問題的答案捎废,TypeScript 需要去檢查 prop 的類型是否可以兼容笑窜。這帶來另一個問題:Source<Source<string>> 和 Target<Target<number>> 的類型是否兼容呢?為了知道這個問題登疗,TypeScript 又去檢查 prop 的情況排截。就帶來另一個遞歸檢查,去檢查 Source<Source<Source<string>>> 和 Target<Target<Target<number>>> 的情況辐益。你會發(fā)現(xiàn)断傲,這會一直迭代下去。</number></string></number></string>
這里智政,TypeScript 需要一些啟發(fā)式的方法艳悔,如果類型檢查展開了足夠的深度,TypeScript 就認為女仰,這個類型有可能可以兼容猜年。這一般來說,是可以的疾忍,但是遺憾的是乔外,有下面的這樣的例子:
interface Foo<T> {
prop: T
}
declare let x: Foo<Foo<Foo<Foo<Foo<Foo<string>>>>>>
declare let y: Foo<Foo<Foo<Foo<Foo<string>>>>>
x = y
一個人類讀者很容易知道,x 和 y 是不兼容的一罩。然而類型是深層嵌套的杨幼,這只是他們的聲明方式。啟發(fā)式檢查并不知道這個聲明方式,而是要一層層進行檢查差购。
TypeScript 4.6 現(xiàn)在可以區(qū)分這樣的例子四瘫,然后對于最后這個例子,可以給出正確的報錯欲逃。由于目前 TypeScript 已經(jīng)不擔心顯示書寫的類型的假誤報找蜜,TypeScript 可以在更早的時候知道一個無限展開的類型,這個給類型檢查的提速也帶來了很多好處稳析。通過這次的優(yōu)化洗做,一些無限類型的庫比如 redux-immutable,react-lazylog 和 yup 有 100% 類型檢查的提速(時間減少 50%)彰居。
這個提升诚纸,你應該已經(jīng)享受到了,因為我們 cherry-picked 到 TypeScript 4.5.3 了陈惰,你可以這這里讀到更詳細的情況畦徘。
索引訪問推斷增強
TypeScript 現(xiàn)在可以正確推斷索引訪問類型,從而映射 mapped object 類型的成員:
interface TypeMap {
number: number
string: string
boolean: boolean
}
type UnionRecord<P extends keyof TypeMap> = {
[K in P]: {
kind: K
v: TypeMap[K]
f: (p: TypeMap[K]) => void
}
}[P]
function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {
record.f(record.v)
}
// 這個在之前會報錯 - 現(xiàn)在是可以工作的!
processRecord({
kind: 'string',
v: 'hello!',
// 'val' used to implicitly have the type 'string | number | boolean',
// but now is correctly inferred to just 'string'.
f: (val) => {
console.log(val.toUpperCase())
},
})
這個模式可以允許 TypeScript 知道 record.f(record.v) 是合法的抬闯,但是之前的版本旧烧,這個是有問題的。
TypeScript 4.6 之后画髓,你不需要在調用 processRecord 前手動去做類型斷言掘剪。
更多信息請參考這里。
Dependent 參數(shù)的控制流分析引擎增強
一個函數(shù)的參數(shù)簽名可以通過展開參數(shù)的 discriminated tuples 聯(lián)合類型來聲明奈虾。
function func(...args: ['str', string] | ['num', number]) {
// ...
}
這樣聲明了以后夺谁,參數(shù)的具體類型,依賴第一個參數(shù)的值肉微。當?shù)谝粋€參數(shù)是 “str” 時匾鸥,第二個參數(shù)就是 string,反之亦然碉纳。
現(xiàn)在這樣的例子勿负,TypeScript 可以正確進行參數(shù)收束。(這個改動的好處是可以少寫函數(shù)重載)
type Func = (...args: ['a', number] | ['b', string]) => void
const f1: Func = (kind, payload) => {
if (kind === 'a') {
payload.toFixed() // 'payload' 收束到 'number'
}
if (kind === 'b') {
payload.toUpperCase() // 'payload' 收束到 'string'
}
}
f1('a', 42)
f1('b', 'hello')
對于更多的信息劳曹,請參照這里奴愉。
-target es2022
TypeScript 的 --target 選項,現(xiàn)在可以使用 es2022 了铁孵。這代表著锭硼,現(xiàn)在 class 字段可以正確輸出。并且一些新的內置函數(shù)都可以使用了蜕劝,比如 Arrays 的 at()檀头,Object 的 hasOwn轰异,新的報錯信息,都可以通過 --target 使用暑始,或者 --lib es2022 來使用搭独。
這個實現(xiàn)是Kagami Sascha Rosylight (saschanaz) 實現(xiàn)的,感謝他的貢獻廊镜。
移除 react-jsx 不必要的參數(shù)
在之前的版本牙肝,當使用 --jsx react-jsx 時,下面的代碼:
export const el = foo
會被 TypeScript 編譯為
import { jsx as _jsx } from "react/jsx-runtime";
export const el = _jsx("div", { children: "foo" }, void 0);
最后 void 0 參數(shù)是沒有必要的期升,所以現(xiàn)在移除這個參數(shù)。
- export const el = _jsx("div", { children: "foo" }, void 0);
+ export const el = _jsx("div", { children: "foo" });
感謝 Alexander Tarasyuk 的 PR互躬。
JSDoc 名字建議
在 JSDoc 里播赁,你可以通過 @param 標簽來標注參數(shù)的類型。
/**
* @param x The first operand
* @param y The second operand
*/
function add(x, y) {
return x + y
}
但是吼渡,如果這些注釋過期了呢容为?比如我們吧 x 和 y 改名為 a 和 b。
/**
* @param x {number} The first operand
* @param y {number} The second operand
*/
function add(a, b) {
return a + b
}
之前寺酪,TypeScript 只會在 JavaScript 文件進行類型檢查坎背,當打開 checkJs 屬性時,或者在一個文件最上面添加 // @ts-check 注釋寄雀。
你現(xiàn)在可以在 TypeScript 文件里也獲得相應的信息得滤。TypeScript 現(xiàn)在會對不匹配的 JSDoc 注釋進行一些提示。
Alexander Tarasyuk 提供了這個變化盒犹。
對于 JavaScript 提供更多的語法和綁定錯誤提示
TypeScript 對了 JavaScript 文件增加了很多語法和報錯的提示懂更。你現(xiàn)在通過 Visual Studio 或者 Visual Studio Code 可以看到這些新的報錯,也可以通過 TypeScript 編譯器跑 JavaScript 代碼來看到這些信息(你都不需要增加 checkJs 或者 // @ts-check)急膀。
例如沮协,如果你聲明兩個同名 const 變量,TypeScript 就會給你報錯卓嫂。
const foo = 1234
// ~~~
// error: Cannot redeclare block-scoped variable 'foo'.
// ...
const foo = 5678
// ~~~
// error: Cannot redeclare block-scoped variable 'foo'.
另一個例子是慷暂,TypeScript 可以讓你知道你的 Modifiers 是不是寫錯了。
function container() {
export function foo() {
// ~~~~~~
// error: Modifiers cannot appear here.
}
}
你可以通過增加 // @ts-nocheck 來關閉這些報錯晨雳,但是我們也想知道行瑞,這些改動,對于 JavaScript 工作流有什么作用餐禁,有任何問題蘑辑,歡迎來反饋。你可以在 Visual Studio Code 里安裝 TypeScript 和 JavaScript 夜間擴展坠宴,和讀這兩個文章洋魂。
TypeScript Trace 分析工具
現(xiàn)在經(jīng)常會有非常耗費性能的類型,讓整個類型檢查很慢。TypeScript 現(xiàn)在有 --generateTrace 標簽來輸出這些昂貴的類型副砍,也可以通過這個報告來診斷一些 TypeScript 編譯器的 issue衔肢。雖然這些信息有用,但是很難讀豁翎。所以現(xiàn)在增加了可視化的觀看方法角骤。
我們最近發(fā)布了一個工具叫 @typescript/analyze-trace ,你可以通過這個工具來看一個圖表的展示方式心剥。我們不期望所有人都需要 analyze-trace邦尊,但是我們認為,這給一些性能問題提供了工具优烧。
對于更多的信息蝉揍,請看analyze-tracetool’s repo
重大改變
解構對象時丟棄范型對象的不可展開成員
現(xiàn)在對象展開時,對于不可展開的成員畦娄,會把對應的類型丟棄:
class Thing {
someProperty = 42
someMethod() {
// ...
}
}
function foo<T extends Thing>(x: T) {
let { someProperty, ...rest } = x
// 之前是通過的又沾,現(xiàn)在會報錯!
// Property 'someMethod' does not exist on type 'Omit'.
rest.someMethod()
}
rest 之前會有 Omit<T,"someProperty"> 類型熙卡,因為 TypeScript 會嚴格分析被解構的類型杖刷。因為在真正展開的運行情況和這個類型是不相符的。所以驳癌,在 4.6 中滑燃,rest 的類型,會變?yōu)?Omit<T,"someProperty"|"someMethod">颓鲜。
這個也會在 this 的解構中生效不瓶。當使用 ...rest 對 this 進行解構時,unspredable 和 non-public 成員灾杰,都會被丟棄蚊丐。
class Thing {
someProperty = 42
someMethod() {
// ...
}
someOtherMethod() {
let { someProperty, ...rest } = this
// 之前是通過的,現(xiàn)在會報錯艳吠!
// Property 'someMethod' does not exist on type 'Omit'.
rest.someMethod()
}
}
對于更多的信息麦备,請看這里。
JavaScript 文件會一直受到語法和綁定的報錯
之前昭娩,TypeScript 會忽略大部分 JavaScript 得語法報錯凛篙,防止與 TypeScript 的語法混淆。現(xiàn)在 TypeScript 可以對 JavaScript 得語法進行校驗栏渺,比如不正確的修飾符呛梆, 重復的聲明,還有更多的東西磕诊。通過使用 Visual Studio Code 或者 Visual Studio 就可以獲得這些能力填物,你也可以通過 TypeScript 編譯器來實現(xiàn)纹腌。
你可以通過在 // @ts-nocheck 在文件頂部來關閉一個文件的檢查。
可以看第一個和第二個這個功能的實現(xiàn)來詳細了解這個功能滞磺。
下一步升薯?
我們希望這次發(fā)布可以給你的代碼之旅帶來更多的快樂。如果你對于下一次發(fā)布也感興趣击困,可以閱讀我們對于 TypeScript 4.7 的規(guī)劃涎劈。
Happy Hacking!
– Daniel Rosenwasser and the TypeScript Team