TypeScript 真的值得嗎?
- 2020 年 2 月 18 日
- 筆記
每日前端夜話第272篇
翻譯:瘋狂的技術宅
作者:Paul Cowan
來源:logrocket

正文共:2005 字
預計閱讀時間:7 分鐘

在開始之前,希望大家知道,我是 TypeScript 愛好者。它是我在前端 React 項目和基於後端 Node 工作時的主要編程語言。但我確實有一些疑惑,所以想在本文中進行討論。迄今為止,我已經用 TypeScript 寫了至少三年的代碼,所以 TypeScript 做得的確不錯,而且滿足了我的需求。
TypeScript 克服了一些很難解決的問題,並成為前端編程領域的主流。TypeScript 在這篇列出了最受歡迎的編程語言的文章【https://www.agiratech.com/most-in-demand-programming-languages-learn-2020/】中排名第七位。
無論是否使用 TypeScript,任何規模的開發團隊都應該遵循以下慣例:
- 編寫良好的單元測試——應在合理範圍內涵蓋儘可能多的生產代碼
- 結對編程——額外的審視可以捕捉到的錯誤遠遠超過語法錯誤
- 良好的同行評審流程——正確的同行評審可以檢查出許多機器無法捕獲的錯誤
- 使用 linter,例如 eslint
TypeScript 可以在這些基礎之上增加額外的安全性,但我認為這在編程語言需求列表中應該排在後面。
TypeScript 不是健全的類型系統
我認為這可能是 TypeScript 當前版本的主要問題,但是首先讓我定義 健全 和 非健全 的類型系統。
健全性
健全的類型系統是能夠確保你的程序不會進入無效狀態的系統。例如,如果表達式中的靜態類型為 string
,則在運行時,要保證在評估它時僅獲得 string
。
在健全的類型系統中,絕對不會在編譯時或運行時產生表達式與預期類型不匹配的情況。
當然 TypeScript 有一定程度的健全性,並捕獲以下類型錯誤:
// 'string' 類型不可分配給 'number' 類型 const increment = (i: number): number => { return i + "1"; } // Argument of type '"98765432"' is not assignable to parameter of type . // 無法將參數類型 '"98765432"' 分配給參數類型'number'。 const countdown: number = increment("98765432");
不健全
100% 的健全性不是 Typescript 的目標,這是在 non-goals of TypeScript【https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals】列表中第 3 條中明確指出的事實:
…適用健全或「證明正確的」類型的系統。相反,要在正確性和生產率之間取得平衡。
這意味着不能保證變量在運行時具有定義的類型。我可以用下面的例子來說明這一點:
interface A { x: number; } let a: A = {x: 3} let b: {x: number | string} = a; b.x = "unsound"; let x: number = a.x; // 不健全的 a.x.toFixed(0); // 什麼鬼?
上面的代碼是 不健全 的,因為從接口 A
中能夠知道 a.x
是一個數字。不幸的是,經過一系列重新分配後,它最終以字符串形式出現,並且以下代碼能夠編譯通過,但是會在運行時出錯。
不幸的是,這裡顯示的表達式可以正確編譯:
a.x.toFixed(0);
我認為這可能是 TypeScript 最大的問題,因為健全性不是目標。我仍然會遇到許多運行時錯誤,tsc
編譯器不會標記這些錯誤。通過這種方法,TypeScript 在健全和不健全的陣營中腳踏兩隻船。這種半途而廢的現象是通過 any
類型強制執行的,我將在後面提到。
我仍然需要編寫很多的測試,這讓我感到沮喪。當我第一次開始使用 TypeScript 時錯誤地得出結論:可以不必編寫這麼多單元測試了。
TypeScript 挑戰了現狀,並聲稱降低使用類型的認知開銷比類型健全性更重要。
我能夠理解為什麼 TypesScript 會走這條路,並且有一個論點指出,如果健全類型系統能夠得到 100% 的保證,那麼對 TypeScript 的使用率講不會那麼高。這種觀點隨着 dart 語言的逐漸流行( Flutter 現已被廣泛使用)被反駁了。健全性是 dart 語言的目標,這裡是相關的討論(https://dart.dev/guides/language/sound-dart)。
不健全以及 TypeScript 暴露在嚴格類型之外的各種轉義符使它的有效性大大降低,不過這總比沒有強一些。我的願望是,隨着 TypeScript 的流行,能夠有更多的編譯器選項可供使用,從而使高級用戶可以得到 100% 的可靠性。
TypeScript 不保證運行時的類型檢查
運行時類型檢查不是 TypeScript 的目標,因此這種願望可能永遠不會實現。例如在處理從 API 調用返回的 JSON 時,運行時類型檢查將是有好處的。如果可以在類型級別上進行控制,則不需要那麼多的錯誤種類和單元測試。
正是因為無法在運行時保證所有的事情,所以可能會發生:
const getFullName = async (): string => { const person: AxiosResponse = await api(); //response.name.fullName 可能會在運行時返回 undefined return response.name.fullName }
儘管有一些很棒的支持庫,例如 io-ts,但這可能意味着你必須複製自己的model。
可怕的 `any` 類型和嚴格性選項
any
類型就是這樣,編譯器允許任何操作或賦值。
TypeScript 在一些小細節上往往很好用,但是人們傾向於在 any
類型上花費很多時間。我最近在一個 Angular 項目中工作,看到很多這樣的代碼:
export class Person { public _id: any; public name: any; public icon: any;
TypeScript 讓你忘記類型系統。
你可以用 any
強制轉換任何一種類型:
("oh my goodness" as any).ToFixed(1); // 還記得我說的健全性嗎?
strict
編譯器選項啟用了以下編譯器設置,這些設置會使事情聽起來更加合理:
--strictNullChecks
--noImplicitAny
--noImplicitThis
--alwaysStrict
還有 eslint 規則 @typescript-eslint/no-explicit-any【https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md】。
any
的泛濫會破壞你類型的健全性。
結論
必須重申,我是 TypeScript 愛好者,而且一直在日常工作中使用它,但是我確實認為它出現的時間還很短,而且類型還並不完全合理。Airbnb 聲稱 TypeScript 可以阻止 38% 的錯誤【https://www.reddit.com/r/typescript/comments/aofcik/38_of_bugs_at_airbnb_could_have_been_prevented_by/】。我非常懷疑這個數字的準確性。TypeScript 不會對現有的做法有良好的提高。我仍然必須編寫儘可能多的測試。你可能會不同意,不過我一直在編寫更多的代碼,並且不得不去編寫類型測試,同時仍然會遇到意外的運行時錯誤。
TypeScript 提供了基本的類型檢查,但健全性和運行時類型檢查不是它的目標,這使 TypeScript 在美好的世界和我們所處的現狀中採取折衷。
TypeScript 的亮點在於有良好的 IDE 支持,例如 vscode,如果我們輸入了錯誤的內容,將會獲得很好的視覺反饋。

vscode中的TypeScript錯誤
通過 TypeScript 還可以增強重構的功能,並且在對修改後的代碼進行編譯時,可以立即識別出代碼的改變(例如方法簽名的更改)。
TypeScript 啟用了良好的類型檢查,並且絕對要比沒有類型檢查或僅使用普通的 eslint 更好,但是我認為它還可以做更多的事情。對於那些想要更多的人來說,還能夠提供足夠多的編譯器選項。
原文:https://blog.logrocket.com/is-typescript-worth-it/