當我們在開發維護一些工具類專案的時候,隨著功能的豐富以及維護人員的變更,會導致程式碼的可持續維護性下降,因此需要一些其他工具來幫我們提高程式碼質量,減少一些不必要的錯誤。本篇我們來介紹使用TS來做一些事情。
什麼是TS
TypeScript 是微軟開發一款開源的程式語言,本質上是向 JavaScript 增加靜態型別系統。它是 JavaScript 的超集,所有現有的 JavaScript 都可以不加改變就在其中使用。它是為大型軟體開發而設計的,它最終編譯產生 JavaScript,所以可以執行在瀏覽器、Node.js 等等的執行時環境。
TS能做什麼
首先TS的定位是靜態型別語言,而不是型別檢查器(對比flow)。 從開發工具提供的能力看也不僅僅是型別檢查,很直觀的就是Intellisense over Compilation Error,當一段程式碼有問題(比如少寫了字母)時,寫完馬上就會有紅色波浪線提示,而不是等到編譯的時候才告訴你哪一行有問題。 因此使用TS提供的型別系統+靜態分析檢查+智慧感知/提示,使大規模的應用程式碼質量更高,執行時bug更少,更方便維護。
對比js有哪些優勢
-
開發效率
雖然需要多寫一些型別定義程式碼,但TS在WebStorm等IDE下可以做到智慧提示,智慧感知bug,同時我們專案常用的一些第三方類庫框架都有TS型別宣告(@types管理),我們也可以給那些沒有TS型別宣告的穩定模組寫宣告檔案,這在團隊協作專案中可以提升整體的開發效率。
-
可維護性
長期迭代維護的專案開發和維護的成員會有很多,人員的不穩定性和團隊成員水平的差異的差異性,以及軟體本身具有熵的特質,導致長期迭代維護的專案總會遇到可維護性逐漸降低的問題。而有了強型別約束和靜態檢查,以及智慧IDE的幫助下,可以降低軟體腐化的速度,提升可維護性。並且如果在重構程式碼時,強型別和靜態型別檢查會幫上大忙,一定程度上減少重構代價。(大家開發維護起來更安全、放心)。
-
線上執行質量
我們現在的工具類專案很多bug都是由於一些呼叫方和被呼叫方的資料格式不匹配引起的。TS可以在編譯期進行靜態檢查,可以在編寫除錯程式碼時就發現這些問題,並且IDE可以智慧糾錯,編碼時就能提前感知bug的存在,我們的線上執行時質量會更為穩定可控。
Flow、babel、tsc
flow用來做型別檢查,比如vue就是用的flow,但是flow也有很多問題:
- 無用的錯誤資訊
比如 Incompatible instantiation for T, T 是一個型別變數,但是你並不能迅速找到這個錯誤在哪裡。
- 執行困難
執行 Flow是需要一定成本的。對於Mac 使用者來說非常幸運,通過 homebrew 可以安裝預製的二進位制包。但如果你需要自己編譯它,你就先得建立一套 OCaml 開發環境。
babel相比於tsc,首先定位是不同的,babel是一種js預處理工具,理論上說完全可以實現對ts的預處理,但是tsc對ts處理會更加精細。當然tsc 的功能沒有 babel 多,擴充套件性也沒有 babel 強。
專案的應用
我們的開源腳手架builder-webpack4已經實踐了幾個月了,為了更好的維護,我們決定遷移到ts。這中間踩了一些坑,但也積累了一些經驗。
- tsconfig配置
ts配置檔案有很多配置項,但是對於我們開發node工具來說其實用到的並不多,我們只需要關注模組化,編譯路徑和輸出路徑即可。 關於模組化,我們希望輸出的是commonjs規範的,至於最終是es5/es6或是其他,因人而異,我們需要配置的就是:
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es5",
"moduleResolution": "node"
}
複製程式碼
編譯路徑和輸出路徑,這裡跟webpack類似,當然這裡的編譯路徑是指定tsc編譯哪些目錄下的ts檔案,否則編譯會因為內容太多而報錯。
"compilerOptions": {
"outDir": "lib" //輸出路徑
},
//編譯目錄
"include": [
"src/**/*"
],
複製程式碼
當然我們還可能會指定types的路徑
"paths": {
"*": [
"node_modules/*",
"src/types/*"
]
}
複製程式碼
- npm包的types
對於多數的npm包來說都可以通過安裝@types/xxx來解決,比如node我們就可以安裝@types/node,當然也有一些@types並沒有做管理,那就需要我們自己來寫一下。舉個例子,webpack處理html相關會用到一個外掛“html-webpack-plugin”,它是作為一個模組來使用,那麼只需要以下宣告即可
declare module 'html-webpack-plugin';
複製程式碼
當然你可能會用到某些UMD的包(既可以當模組又可以作為全域性變數使用):
declare namespace UMD{
//可以定義一些其他東西
}
複製程式碼
- interface(介面)
比如我們有些方法需要修改一些引數,使用TypeScript 之後,把資料對應的 interface 改掉,然後重新編譯一次,把編譯失敗的地方全部改掉就好了。 對於builder-webpack4來說很多方法的引數都較為複雜,比如我們生成構建配置檔案的時候,webpack的配置老多了,自然是需要寫個interface來控制,但是問題是如果別的模組呼叫這個方法又得重寫一次?不用的,只要吧interface當作模組一樣匯出即可(當然你也可以把這些寫到d.ts中,然後引入到對應的檔案)。
export interface xxx{
[propName: string]: any
}
複製程式碼
- 靜態型別檢查
當我們開始盲寫程式碼的時候,總會不可避免的有些小失誤,那麼利用IDE配合ts提供的工具就可以幫我們提前發現一些問題;在編譯除錯中同樣可以發現一些未觸及的點。
我們在呼叫方法的時候就知道這個方法需要哪些引數,當然如果型別寫錯了就立馬會有紅色波浪線標註出來(格外的扎眼)。
- 程式碼質量的提升
作為一種弱型別語言,js開發一些大型/持續維護專案的時候,經常會讓人體驗什麼是“開發一時爽,重構火葬場”。
- 其他注意點
對於模組的匯出
export default builderWebpack4;
複製程式碼
這個玩意編譯出來其實是這樣子的
exports.default = builderWebpack4;
複製程式碼
但是對於別的呼叫方來說並不能直接用,我們想要的是
module.exports = builderWebpack4;
複製程式碼
所以原始碼中多加個指定就好了
module.exports = exports.default;
複製程式碼
當然對於專案內部間模組呼叫是不需要的,tsc構建會生成相關的hack
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var webpack_1 = __importDefault(require("webpack"));
複製程式碼
什麼時候需要用
- 大型專案
專案越大,越難以維護,因此對於多人寫協作的大型專案有必要使用ts來提高程式碼質量。
- 持續維護的專案
對於一些長久執行的專案,既要保證它的穩定執行,又要保證專案交接便捷(可維護性),使用ts是目前來看最好選擇。
- 工具類專案
使用nodejs/js寫一些前端工具或者庫的時候,同樣是需要關注以上兩點內容,而且工具類的專案影響範圍較大,在開發維護中要更加謹慎,那麼使用ts幫我們儘量減少一些低階錯誤是很有必要的。
關注公眾號:【IVWEB社群】,每週推送精品技術週刊 。