做者簡介:aoto 螞蟻金服·數據體驗技術團隊javascript
Q:爲何要寫這邊文章?這篇文章要表達什麼?html
A:咱們考慮在SPA應用中使用TS做爲開發語言,咱們須要一篇系統性介紹TS自己及周邊的文章來論證在項目中使用TS做爲開發語言是科學合理的,並且是順勢而爲的。前端
TypeScript = Type + Script(標準JS)。咱們從TS的官方網站上就能看到定義:TypeScript is a typed superset of JavaScript that compiles to plain JavaScript
。TypeScript是一個編譯到純JS的有類型定義的JS超集。java
目標:生命週期較長(經常持續幾年)的複雜SPA應用,保障開發效率的同時提高代碼的可維護性和線上運行時質量。react
一個複雜軟件的常規研發流程,大體分爲定義問題、需求分析、規劃構建、軟件架構、詳細設計、編碼調試、單元測試、集成測試、集成、系統測試、保障維護。構建活動(主要是編碼調試)在中大型項目中的工做量佔比大於50%。同時,一箇中大型項目,bug由構建階段引發的比例佔到50%~75%,對於一個成功的項目來講,構建活動是必需要作的,並且是工程師更爲可控的。【代碼大全】webpack
TS適合大規模JavaScript應用,正如他的官方宣傳語JavaScript that scales
。從如下幾點能夠看到TS在團隊協做、可維護性、易讀性、穩定性(編譯期提早暴露bug)等方面上有着明顯的好處:git
這類問題是ESLint等工具檢測不出來的。程序員
const peoples = [{ name: 'tim', age: 20 }, { name: 'alex', age: 22 }]; const sortedPeoples = peoples.sort((a, b) => a.name.localCompare(b.name));
執行TS編譯命令tsc
,檢測到錯誤:github
error TS2339: Property 'localCompare' does not exist on type 'string'.
若是是在支持TS的IDE中(VS Code、WebStorm等),則不需等到編譯,在IDE中就能夠很是明顯在localCompare位置提示出錯誤信息。web
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自己是JS的超集,這也意味着他自己是能夠支持現有JS代碼的,至少理論上是這樣。學習一下類型系統的相關知識和麪向對象的基礎知識,應該能夠hold住TS,成本不會很高。官方文檔是最好的學習材料。
對於老項目,因爲TS兼容ES規範,因此能夠比較方便的升級現有的JS(這裏指ES6及以上)代碼,逐漸的加類型註解,漸進式加強代碼健壯性。遷移過程:
tsc --init
,自動產生tsconfig.json
文件。"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編譯,這步省略)
json loaders: [ // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. { test: /\.tsx?$/, loader: "awesome-typescript-loader" } ]
逐漸的,開始打算重構之前的ES6代碼爲TS代碼,只需將文件後綴改爲ts(x)就行,就能夠享受TS及IDE智能感知/糾錯帶來的好處。
更多遷移教程:官方遷移教程、官方React項目遷移教程、社區教程1、社區教程2。
星多表示佔優
成本點 | 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
方式安裝,能夠在這個網站搜索你想要安裝的庫聲明包。安裝後,寫和那些框架、庫相關的代碼將會是一種很是爽的體驗,函數的定義和註釋將會自動提示出來,開發效率將會獲得提高。
VS Code、WebStorm等前端圈流行的IDE都對TS有着很是友好的支持,VS Code甚至自身就是TS寫成的。
編譯期能夠作靜態檢查,爲大規模代碼提供結構化裝置,編譯出符合習慣、易讀的JS代碼,和ECMAScript標準對齊,使用一向的、可刪除的、結構化的類型系統,保護編譯後的JS代碼的運行時行爲等等。
模仿現有語言,優化編譯後代碼的性能,應用「正確」的類型系統,增長運行時類型信息等等。
在最近幾年,隨着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】
從這篇文章能夠看出,基礎的類型檢查功能發展到如今已經差異不大了,但在周邊生態、文檔完整性、社區資源方面TS賽過Flow。
Babel也是很不錯的ES6 to 5編譯工具,有不錯的插件機制,社區發展也不錯,但在一樣一段代碼編譯出的JS代碼裏能夠看到,TS編譯後的代碼是更符合習慣、簡潔易讀一些(都用的是官方網站的Playground工具)。我曾經維護過TS編譯後的JS代碼(TS源碼丟失),感受還OK。
Babel編譯後:
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; // 不肯定的能夠先聲明爲any let 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;
更多介紹能夠直接看官方文檔。
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兩個接口。
導出到外部的模塊寫法和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等都和ES六、ES7寫法相似。
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 it
is 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, safe
languages 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.【維基百科】
靜態類型檢查會提供有用的編譯期信息。例如,若是一個類型須要在內存中佔四個字節,編譯器可能會使用更有效率的機器指令。
1+'1'='11'
,數字型轉成了字符型。1+'1'
會拋出TypeError
。TS剛出來時我是有點抵觸的,或者對她的感受就跟和CoffeeScript
、Dart
等編譯到JS語言差很少,感受就是其餘語言往JS滲透的產物,近一兩年,社區中TS的聲音愈來愈強,而我也開始作大型JavaScript應用,隨之逐漸從新認識TS,逐漸認識到TS的類型系統、TSC的靜態檢查、VS Code等IDE的強力支持對於開發出可維護性好、穩定性高的大型JavaScript應用的重要性。
如何更好的利用JS的動態性和TS的靜態特質,咱們須要結合項目的實際狀況來進行綜合判斷。一些建議:
至於到底用不用TS,仍是要看實際項目規模、項目生命週期、團隊規模、團隊成員狀況等實際狀況綜合考慮。