作者簡介:aoto 螞蟻金服·資料體驗技術團隊
Q:為什麼要寫這邊文章?這篇文章要表達什麼?
A:我們考慮在SPA應用中使用TS作為開發語言,我們需要一篇系統性介紹TS本身及周邊的文章來論證在專案中使用TS作為開發語言是科學合理的,而且是順勢而為的。
導引
- TS是什麼
- 為什麼要用TS
- TS能幹點什麼
- 使用TS的成本
- 社群發展
- 周邊生態
- 深入解讀TS
- 接受TS
- 權衡
TS是什麼
TypeScript = Type + Script(標準JS)。我們從TS的官方網站上就能看到定義:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript
。TypeScript是一個編譯到純JS的有型別定義的JS超集。
為什麼要用TS
目標:生命週期較長(常常持續幾年)的複雜SPA應用,保障開發效率的同時提升程式碼的可維護性和線上執行時質量。
- 從開發效率上看,雖然需要多寫一些型別定義程式碼,但TS在VSCode、WebStorm等IDE下可以做到智慧提示,智慧感知bug,同時我們專案常用的一些第三方類庫框架都有TS型別宣告,我們也可以給那些沒有TS型別宣告的穩定模組寫宣告檔案,如我們的前端KOP框架(目前還是螞蟻內部框架,類比dva),這在團隊協作專案中可以提升整體的開發效率。
- 從可維護性上看,長期迭代維護的專案開發和維護的成員會有很多,團隊成員水平會有差異,而軟體具有熵的特質,長期迭代維護的專案總會遇到可維護性逐漸降低的問題,有了強型別約束和靜態檢查,以及智慧IDE的幫助下,可以降低軟體腐化的速度,提升可維護性,且在重構時,強型別和靜態型別檢查會幫上大忙,甚至有了型別定義,會不經意間增加重構的頻率(更安全、放心)。
- 從線上執行時質量上看,我們現在的SPA專案的很多bug都是由於一些呼叫方和被呼叫方(如元件模組間的協作、介面或函式的呼叫)的資料格式不匹配引起的,由於TS有編譯期的靜態檢查,讓我們的bug儘可能消滅在編譯器,加上IDE有智慧糾錯,編碼時就能提前感知bug的存在,我們的線上執行時質量會更為穩定可控。
一個複雜軟體的常規研發流程,大致分為定義問題、需求分析、規劃構建、軟體架構、詳細設計、編碼除錯、單元測試、整合測試、整合、系統測試、保障維護。構建活動(主要是編碼除錯)在中大型專案中的工作量佔比大於50%。同時,一箇中大型專案,bug由構建階段引起的比例佔到50%~75%,對於一個成功的專案來說,構建活動是必須要做的,而且是工程師更為可控的。【程式碼大全】
TS適合大規模JavaScript應用,正如他的官方宣傳語JavaScript that scales
。從以下幾點可以看到TS在團隊協作、可維護性、易讀性、穩定性(編譯期提前暴露bug)等方面上有著明顯的好處:
- 加上了型別系統,對於閱讀程式碼的人和編譯器都是友好的。對閱讀者來說,型別定義加上IDE的智慧提示,增強了程式碼的易讀型;對於編譯器來說,型別定義可以讓編譯器揪出隱藏的bug。
- 型別系統+靜態分析檢查+智慧感知/提示,使大規模的應用程式碼質量更高,執行時bug更少,更方便維護。
- 有類似VSCode這樣配套的IDE支援,方便的檢視型別推斷和引用關係,可以更方便和安全的進行重構,再也不用全域性搜尋,一個個修改了。
- 給應用配置、應用狀態、前後端介面及各種模組定義型別,整個應用都是一個個的型別定義,使協作更為方便、高效和安全。
TS能幹點什麼
靜態檢查
這類問題是ESLint等工具檢測不出來的。
低階錯誤
const peoples = [{
name: 'tim', age: 20
}, {
name: 'alex', age: 22
}];
const sortedPeoples = peoples.sort((a, b) =>
a.name.localCompare(b.name));
複製程式碼
執行TS編譯命令tsc
,檢測到錯誤:
error TS2339: Property 'localCompare' does not exist on type 'string'.複製程式碼
如果是在支援TS的IDE中(VS Code、WebStorm等),則不需等到編譯,在IDE中就可以非常明顯在localCompare位置提示出錯誤資訊。
localCompare這種輸入手誤(或者手滑不小心刪除或新增了字元)時有發生,如果沒有編譯器靜態檢查,那有可能就是一個字元引發的血案:埋下了一個隱藏的
執行時bug。如果在SPA應用中,這個問題需要較長的操作路徑才能被發現,一旦使用者觸發這個地雷,那它就會爆炸:應用直接crash(在沒有頁面重新整理的SPA中問題尤為凸顯)。
非空判斷
let data = {
list: null, success: true
};
const value = data.list.length;
複製程式碼
執行tsc
編譯:
error TS2532: Object is possibly 'null'.複製程式碼
data.list.length
這行直接引用了data.list的屬性,但data.list的資料格式有不是陣列的可能性,這種場景在前端處理後端介面返回時經常出現,介面返回的資料層級可能非常深,如果在某一級缺少了非空判斷邏輯,那就意味著埋下了一個不知道什麼時候就會引爆的炸彈。
型別推斷
const arr = [];
arr.toUpperCase();
class Cat {
miao() {
}
}class Dog {
wang() {
}
}const cat = new Cat();
cat.wang();
複製程式碼
執行tsc
編譯:
error TS2339: Property 'toUpperCase' does not exist on type 'any[]'.error TS2339: Property 'wang' does not exist on type 'Cat'.複製程式碼
TS有型別推斷,給不同型別的執行物件呼叫錯誤的方法都將被檢查出來。
物件導向程式設計增強
訪問許可權控制
class Person {
protected name: string;
public age: number;
constructor(name: string) {
this.name = name;
}
}class Employee extends Person {
static someAttr = 1;
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
}let howard = new Employee("Howard", "Sales");
console.log(howard.name);
複製程式碼
執行tsc
編譯:
error TS2445: Property 'name' is protected and only accessible within class 'Person' and its subclasses.複製程式碼
Person中name屬性是protected型別,只能在自己類中或者子類中使用。訪問許可權控制在物件導向程式設計中很有用,他能幫忙我們做到資訊隱藏,JS物件導向程式設計的一個大問題就是沒有提供原生支援資訊隱藏的方案(很多時候都是通過約定編碼方式來做)。資訊隱藏有助於更好的管理系統的複雜度,這在軟體工程中顯得尤為重要。
介面
interface Machine {
move(): void
}interface Human {
run(): void
}class Base {
}class Robot extends Base implements Machine, Human {
run() {
console.log('run');
} move() {
console.log('move');
}
}複製程式碼
Robot類可以繼承Base類,並實現Machine和Human介面,這種可以組合繼承類和實現介面的方式使物件導向程式設計更為靈活、可擴充套件性更好。
泛型
class GenericNumber<
T>
{
zeroValue: T;
add: (x: T, y: T) =>
T;
}let myGenericNumber = new GenericNumber<
number>
();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
return x + y;
};
複製程式碼
定義了一個模板型別T,例項化GenericNumber類時可以傳入內建型別或者自定義型別。泛型(模板)在傳統物件導向程式語言中是很常見的概念了,在程式碼邏輯是通用模式化的,引數可以是動態型別的場景下比較有用。
型別系統
interface SystemConfig {
attr1: number;
attr2: string;
func1(): string;
}interface ModuleType {
data: {
attr1?: string, attr2?: number
}, visible: boolean
}const config: SystemConfig = {
attr1: 1, attr2: 'str', func1: () =>
''
};
const mod: ModuleType = {
data: {
attr1: '1'
}, visible: true
};
複製程式碼
我們定義了一個系統配置型別SystemConfig
和一個模組型別ModuleType
,我們在使用這些型別時就不能隨便修改config
和mod
的資料了。每個被呼叫方負責自己的對外型別展現,呼叫者只需關心被呼叫方的型別,不需關心內部細節,這就是型別約束的好處,這對於多人協作的團隊專案非常有幫助。
模組系統增強
namespace N {
export namespace NN {
export function a() {
console.log('N.a');
}
}
}N.NN.a();
複製程式碼
TS除了支援ES6的模組系統之外,還支援名稱空間。這在管理複雜模組的內部時比較有用。
使用TS的成本
學習成本
理論上學習並應用一門新語言是需要很高成本的,但好在TS本身是JS的超集,這也意味著他本身是可以支援現有JS程式碼的,至少理論上是這樣。學習一下型別系統的相關知識和麵向物件的基礎知識,應該可以hold住TS,成本不會很高。官方文件是最好的學習材料。
應用成本
老專案
對於老專案,由於TS相容ES規範,所以可以比較方便的升級現有的JS(這裡指ES6及以上)程式碼,逐漸的加型別註解,漸進式增強程式碼健壯性。遷移過程:
-
npm全域性安裝typescript包,並在工程根目錄執行
tsc --init
,自動產生tsconfig.json
檔案。預設的3個配置項:更多配置項說明"target":"es5"
: 編譯後程式碼的ES版本,還有es3,es2105等選項。"module":"commonjs"
:編譯後程式碼的模組化組織方式,還有amd,umd,es2015等選項。"strict":true
:嚴格校驗,包含不能有沒意義的any,null校驗等選項。
-
初始化得到的
tsconfig.json
無需修改,增加"allowJs": true
選項。 -
配置webpack配置,增加ts的loader,如awesome-typescript-loader。(如果是基於atool-build來構建的專案,則它內建了ts編譯,這步省略)
loaders: [ // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. {
test: /\.tsx?$/, loader: "awesome-typescript-loader"
}]複製程式碼 -
此時你可以寫檔名為ts和tsx(React)字尾的程式碼了,它可以和現有的ES6程式碼共存,VSCode會自動校驗這部分程式碼,webpack打包也沒問題了。
-
逐漸的,開始打算重構以前的ES6程式碼為TS程式碼,只需將檔案字尾改成ts(x)就行,就可以享受TS及IDE智慧感知/糾錯帶來的好處。
更多遷移教程:官方遷移教程、官方React專案遷移教程、社群教程1、社群教程2。
新專案
- 對於新專案,微軟提供了非常棒的一些Starter專案,詳細介紹瞭如何用TS和其他框架、庫配合使用。如果是React專案,可以參考這個Starter:TypeScript-React-Starter
成本對比
星多表示佔優
成本點 | ES | TS | 說明 |
---|---|---|---|
學習和踩坑成本 | ※※※※※ | ※※※ | 雖然是JS超集,但還是要學習TS本身及物件導向基礎知識,開發環境搭建、使用中的問題和坑也需要自己趟,好在TS社群比較成熟,網上沉澱的資料很多 |
整體程式碼量 | ※※※※※ | ※※※※ | TS程式碼增加比較完善的型別定義的話整體程式碼量比原生ES多5%~10%左右 |
原生JS(標準ES、瀏覽器端、伺服器端) | ※※※ | ※※※※※ | IDE內建了詳盡的型別宣告,可以智慧提示方法和引數說明,提升了效率 |
依賴外部庫(React、Lodash、Antd) | ※※※ | ※※※※※ | 有TS型別宣告庫,IDE智慧提示和分析,效率提升 |
內部公共庫、模組 | ※※※ | ※※※※ | 團隊內部自行編寫型別定義檔案,有一定工作量,但開發效率可以有一些提升,逐步完善型別定義後,效率進一步提升 |
團隊協作效率 | ※※ | ※※※※※ | 對系統配置、外部介面、內部模組做型別定義後,例項物件屬性就不能隨意改了,每個被呼叫方負責自己的對外型別展現(可以理解為形狀),呼叫者只需關心被呼叫方的型別,不需關心內部細節 |
程式碼可維護性 | ※※ | ※※※※ | 由於團隊成員水平差異,和軟體的熵的特質,長期迭代維護的專案總會遇到可維護性的問題,有了強型別約束和靜態檢查,以及智慧IDE的幫助下,可以降低軟體腐化的速度,提升可維護性,且在重構時,強型別和靜態型別檢查會幫上大忙,甚至有了型別定義,會不經意間增加重構的頻率 |
執行時穩定性 | ※※ | ※※※※ | 由於TS有靜態型別檢查,很多bug都會被消滅在上線前 |
小結
從上面的對比中可以看到,使用大家都熟悉的ES作為開發語言只在學習和踩坑成本以及整體程式碼量上佔優,如果只是短期專案,那用ES無可厚非,但我們的專案生命週期持續好幾年,是持續迭代升級的,目前TS社群已經比較成熟,學習資料也很多,而且TS帶來的是內部協作開發效率、可維護性、穩定性的提升,所以從長遠來看這個代價是值得付出的。而且各種型別宣告定義檔案的存在,是可以提升開發效率的;而且靜態型別檢查可以減少bug數量和排查bug的難度,變相也提升了效率,而且使整個專案相對變得更為穩定可控。
社群發展
從Stackoverflow的2017年開發者調查報告、Google趨勢、npm下載量趨勢上可以到看,TypeScript社群發展很快,特別是最近幾年。特別是伴隨著VS Code的誕生(TS寫的,對TS支援非常友好),VS Code + TypeScript的組合讓前端圈產生了一股清流,生產力和規範性得到了快速提升。從Google對TS的支援(Angular高於2的版本是TS寫的)看到,國際大廠也是支援的。
從螞蟻集團內部看,Ant Design、Basement等產品也是基於TS寫的(至少是在大量使用),雖然有一些反對的聲音,但總體還是看好的,有合適的土壤就會快速發展,如Ant Design。
周邊生態
型別宣告包
React、及其他各種著名框架、庫都有TS型別宣告,我們可以在專案中通過npm install @types/react
方式安裝,可以在這個網站搜尋你想要安裝的庫宣告包。安裝後,寫和那些框架、庫相關的程式碼將會是一種非常爽的體驗,函式的定義和註釋將會自動提示出來,開發效率將會得到提升。
IDE
VS Code、WebStorm等前端圈流行的IDE都對TS有著非常友好的支援,VS Code甚至自身就是TS寫成的。
深入解讀TS
TS語言設計目標
目標
編譯期可以做靜態檢查,為大規模程式碼提供結構化裝置,編譯出符合習慣、易讀的JS程式碼,和ECMAScript標準對齊,使用一貫的、可刪除的、結構化的型別系統,保護編譯後的JS程式碼的執行時行為等等。
非目標
模仿現有語言,優化編譯後程式碼的效能,應用“正確”的型別系統,增加執行時型別資訊等等。
TS簡史
在最近幾年,隨著V8平臺、各大現代瀏覽器的起來,JS的執行平臺再不斷完善,但,JS對於大型應用的開發是非常困難的,JS語言設計出來的目的不是為了大型應用,他是一門指令碼語言,他沒有靜態型別校驗,但更重要的是,他沒有提供大型應用必須的classes、modules/namespaces、interfaces等結構化的裝置,中間也出來過GWT等為了其他語言開發者開發大型JS應用的專案,這些專案可以讓你利用Java等面嚮物件語言開發大型Web應用,也可以利用到Eclipse等好用的IDE,但這些專案不是用JS寫程式碼,所以如果你想用JS裡的一些東西,你可能要比較費勁的在其他語言裡把它給實現出來,所以我們考慮如何增強JS語言,提供如靜態型別檢查、classes、modules/namespaces、interfaces等大型應用裝置,這就是TS語言:**TS是一種開發大型JS應用的語言,更詳細一點來說,TS是有型別的編譯到純JS的JS超集。**所以一般來說,JS程式碼也是TS程式碼。本身TS編譯器也是TS寫的,執行Node.js環境。【Anders Hejlsberg: Introducing TypeScript 2012】
TS作者在最近微軟Build大會給出的一個圖:
如圖,Web和Node平臺的JS始終與JS最新規範有一段距離,Web平臺的距離更遠,TS可以填充這個間隙,讓使用者在Web和Node平臺都能用上最新的Feature,用上優雅的JS,提高生產力。【Anders Hejlsberg: What’s new in TypeScript? 2017】
和Flow 、Babel的對比
vs Flow
從這篇文章可以看出,基礎的型別檢查功能發展到現在已經差別不大了,但在周邊生態、文件完整性、社群資源方面TS勝過Flow。
vs Babel
Babel也是很不錯的ES6 to 5編譯工具,有不錯的外掛機制,社群發展也不錯,但在同樣一段程式碼編譯出的JS程式碼裡可以看到,TS編譯後的程式碼是更符合習慣、簡潔易讀一些(都用的是官方網站的Playground工具)。我曾經維護過TS編譯後的JS程式碼(TS原始碼丟失),感覺還OK。
Babel編譯後:
TS編譯後:
TS基礎知識及核心概念
基礎型別
let isDone: boolean = false;
let decimal: number = 6;
let color: string = "blue";
// 陣列,有兩種寫法let list: number[] = [1, 2, 3];
let list: Array<
number>
= [1, 2, 3];
// 元組(Tuple)let x: [string, number] = ["hello", 10];
// 列舉enum Color {Red = 1, Green = 2, Blue = 4
}let c: Color = Color.Green;
// 不確定的可以先宣告為anylet notSure: any = 4;
// 宣告沒有返回值function warnUser(): void {
alert("This is my warning message");
}let u: undefined = undefined;
let n: null = null;
// 型別永遠沒返回function error(message: string): never {
throw new Error(message);
}// 型別主張,就是知道的比編譯器多,主動告訴編譯器更多資訊,有兩種寫法let someValue: any = "this is a string";
let strLength: number = (<
string>
someValue).length;
let strLength: number = (someValue as string).length;
複製程式碼
更多介紹可以直接看官方文件。
interface
TS中的一個核心原則之一就是型別檢查關注的是值的形狀,有時就叫做“鴨子辨型”(duck typing)或“結構化子型別”(structural subtyping)。TS中interface就承擔了這樣的角色,定義形狀與約束,在內部使用或者和外部系統協作。一個例子:
interface SystemConfig {
attr1: string;
attr2: number;
func1(): string;
func2(): void;
}複製程式碼
我們給軟體定了一個系統引數的配置介面,他定義了系統配置的形狀,有兩個屬性attr1
和attr2
,兩個方法func1
和func2
,這樣如果定義了const systemConfig: SystemConfig = {
,那systemConfig就不能隨意修改了,他有形狀了。
}
在Java中,我們提倡面向介面程式設計,介面優於抽象類【Effective Java】,在TS的類系統中,介面也可以承擔這樣的角色,我們可以用implements
來實現介面,這樣可以實現類似更為靈活的繼承,如:
class A extends BaseClass implements BaseInterface1, BaseInterface2 {
}複製程式碼
類A繼承了BaseClass,並且繼承了BaseInterface1和BaseInterface2兩個介面。
module/namespace
匯出到外部的模組寫法和ES6一樣,內部模組現在推薦用namespace,如:
namespace Module1 {
export interface SubModule1 {
} export interface SubModule2 {
}
}const module: Module1.SubModule = {
}複製程式碼
名稱空間在JS中用物件字面量也可以實現,早些年的很多JS庫都是這種模式,但顯然有了這種顯示的名稱空間宣告,程式碼的易讀性更好,且不能隨意的改變,不像用原生JS物件時容易被覆蓋。
訪問許可權控制
TS有著和傳統面嚮物件語言類似的public、protected、private等訪問許可權,這個在大型應用中很實用,這裡不展開。
其他
除了泛型相對難以掌握,其他class、decorator、async/await等都和ES6、ES7寫法類似。
Type和Type System
TypeScript = Type + Script,那麼程式語言中的Type是怎麼定義的呢?
In computer science and computer programming, a data type or simply type is a classification of data which tells the compiler or interpreter how the programmer intends to use the data.【維基百科】
在電腦科學中,資料型別或者簡單說型別,是資料的類別,用來告訴編譯器/直譯器程式設計師想怎麼使用資料。基本的資料型別如整數、布林值、字元等,組合資料型別如陣列、物件等,也有抽象資料型別如佇列、棧、集合、字典等等。資料型別用在型別系統中,型別系統提供了型別定義、實現和使用的方式,每種程式語言都有各自的型別系統實現(如果有的話)。
我們來看看Type System(型別系統)的定義:
In programming languages, a type system is a set of rules that assigns a property called type to the various constructs of a computer program, such as variables, expressions, functions or modules.[1] These types formalize and enforce the (otherwise implicit) categories the programmer uses for data structures and components (ex: “string”, “array of float”, “function returning boolean”). The main purpose of a type system is to reduce possibilities for bugs in computer programs[2] by defining interfaces between different parts of a computer program, and then checking that the parts have been connected in a consistent way. 【維基百科】
在程式語言中,型別系統是一個規則集合,給程式中的變數、表示式、函式、模組等程式構建元素分配叫做型別的屬性。這些型別明確並強制(也可能是含蓄的)程式設計師如何使用資料結構。**型別系統的主要目的是通過定義程式不同部分間協作的介面,並檢查不同部分以始終如一的方式協作,來減少程式可能產生的bug。**這種檢查可能是靜態的(編譯期)或動態的(執行時),或者既有靜態的也有動態的。
型別系統的好處
檢測錯誤
The most obvious benefit of static typechecking is that it allows early detection of some programming errors. Errors that are detected early can be fixed immediately, rather than lurking in the code to be discovered much later,when the programmer is in the middle of something else—or even after the program has been deployed. Moreover, errors can often be pinpointed more accurately during typechecking than at run time, when their effects may not become visible until some time after things begin to go wrong.【Types and Programming Languages】
As your app grows, you can catch a lot of bugs with typechecking. 【React typechecking】
靜態型別檢查最明顯的好處是可以儘早的檢查出程式中的錯誤。錯誤被儘早的檢查出來可以使它得到快速的修復,而不是潛伏在程式碼中,在中期甚至部署上線後才被發現。而且,錯誤在編譯期可以被更精確的定位出來,而在執行時,錯誤產生的影響在程式出現問題前可能是不容易被發現的。
程式會有各種各樣的資料結構,如果改了一個資料型別,前端很多時候都是通過全域性查詢來處理這種重構問題的。而靜態型別檢查則可以使再次編譯後就能探知所有可能的錯誤,加上IDE的智慧錯誤提示,重構起來更放心、更方便。
抽象化
Another important way in which type systems support the programming process is by enforcing disciplined programming. In particular, in the context of large-scale software composition, type systems form the backbone of the module languages used to package and tie together the components of large systems. Types show up in the interfaces of modules (and related structures such as classes);
indeed, an interface itself can be viewed as “the type of a module,” providing a summary of the facilities provided by the module—a kind of partial contract between implementors and users.Structuring large systems in terms of modules with clear interfaces leads to a more abstract style of design, where interfaces are designed and discussed independently from their eventual implementations. More abstract thinking about interfaces generally leads to better design.【Types and Programming Languages】
型別系統支援程式設計階段的另外一個重要方式是強制讓程式設計遵守紀律。在大規模軟體系統中,型別系統組成了元件協作系統的脊樑。型別展現在模組(或者相關的結構如類)的介面中。介面可以看做“模組的型別”,展示了模組所能提供的功能,是一種實現者和使用者間的合約。
在大量模組協作組成的大規模結構化軟體系統中清晰的介面可以使設計更為抽象,介面的設計和討論獨立於它們的實現。一般來說,對介面更為抽象的思考可以使得做出更好的設計。
Types enable programmers to think at a higher level than the bit or byte, not bothering with low-level implementation. For example, programmers can begin to think of a string as a set of character values instead of as a mere array of bytes. Higher still, types enable programmers to think about and express interfaces between two of any-sized subsystems. This enables more levels of localization so that the definitions required for interoperability of the subsystems remain consistent when those two subsystems communicate.【維基百科】
型別會讓程式設計師在一個更高的維度思考,而不是在底層的計算機實現細節糾纏。例如,我們可以把字串想成字符集,而不僅僅是位元陣列。更高維度,型別系統可以讓我們用介面來思考和表達任意子系統/子程式之間的協作,介面定義可以讓子系統/子程式之間的通訊方式始終如一。
文件化
Types are also useful when reading programs. The type declarations in procedure headers and module interfaces constitute a form of documentation,giving useful hints about behavior. Moreover, unlike descriptions embedded in comments, this form of documentation cannot become outdated, since itis checked during every run of the compiler. This role of types is particularly important in module signatures.【Types and Programming Languages】
型別對於閱讀程式也是有用的。在程式頭部的型別宣告和模組介面形成了文件的形狀,提供程式的行為提示。此外,不同於在註釋中的描述,這種形式的文件不會過期,因為每次編譯都會校驗,這在模組簽名裡特別重要。
In more expressive type systems, types can serve as a form of documentation clarifying the intent of the programmer. For example, if a programmer declares a function as returning a timestamp type, this documents the function when the timestamp type can be explicitly declared deeper in the code to be an integer type.【維基百科】
在複用表現力的型別系統中,型別可以看做是一種描述程式設計師意圖的表述方式。例如,我們宣告一個函式返回一個時間戳,這樣就相當於明確說明了這個函式在更深層次的程式碼呼叫中會返回整數型別。
語言安全
The term “safe language” is, unfortunately, even more contentious than “type system.” Although people generally feel they know one when they see it, their notions of exactly what constitutes language safety are strongly influenced by the language community to which they belong. Informally, though, safelanguages can be defined as ones that make it impossible to shoot yourself in the foot while programming.【Types and Programming Languages】
安全語言這個說法是有爭議的。受到該語言社群的嚴重影響。不正式的來說,安全語言可以被定義為在程式設計時不可能從底層把自己殺死。
A type system enables the compiler to detect meaningless or probably invalid code. For example, we can identify an expression
3 / "Hello, World"
as invalid, when the rules do not specify how to divide an integer by a string. Strong typing offers more safety, but cannot guarantee complete type safety.【維基百科】
型別系統會允許編譯器檢查無意義或者可能不合法的程式碼。例如,我們知道3/'hello world'
不合法,強型別提供了更多的安全性,但也不能完全做到型別安全。
效率
Static type-checking may provide useful compile-time information. For example, if a type requires that a value must align in memory at a multiple of four bytes, the compiler may be able to use more efficient machine instructions.【維基百科】
靜態型別檢查會提供有用的編譯期資訊。例如,如果一個型別需要在記憶體中佔四個位元組,編譯器可能會使用更有效率的機器指令。
靜態型別、動態型別和弱型別、強型別
- 靜態型別:編譯期就知道每一個變數的型別。型別錯誤編譯失敗是語法問題。如Java、C++。
- 動態型別:編譯期不知道型別,執行時才知道。型別錯誤丟擲異常發生在執行時。如JS、Python。
- 弱型別:容忍隱式型別轉換。如JS,
1+'1'='11'
,數字型轉成了字元型。 - 強型別:不容忍隱式型別轉換。如Python,
1+'1'
會丟擲TypeError
。
接受TS
TS剛出來時我是有點牴觸的,或者對她的感覺就跟和CoffeeScript
、Dart
等編譯到JS語言差不多,感覺就是其他語言往JS滲透的產物,近一兩年,社群中TS的聲音越來越強,而我也開始做大型JavaScript應用,隨之逐漸重新認識TS,逐漸認識到TS的型別系統、TSC的靜態檢查、VS Code等IDE的強力支援對於開發出可維護性好、穩定性高的大型JavaScript應用的重要性。
權衡
如何更好的利用JS的動態性和TS的靜態特質,我們需要結合專案的實際情況來進行綜合判斷。一些建議:
- 如果是中小型專案,且生命週期不是很長,那就直接用JS吧,不要被TS束縛住了手腳。
- 如果是大型應用,且生命週期比較長,那建議試試TS。開源專案如VS Code、GitHub桌面端,不開源的如Slack桌面端、Asana、Palantir。
- 如果是框架、庫之類的公共模組,那更建議用TS了。如Ant Design、Angular 、Ionic。
至於到底用不用TS,還是要看實際專案規模、專案生命週期、團隊規模、團隊成員情況等實際情況綜合考慮。