當(dāng)我們?cè)陂_(kāi)發(fā)維護(hù)一些工具類項(xiàng)目的時(shí)候,隨著功能的豐富以及維護(hù)人員的變更,會(huì)導(dǎo)致代碼的可持續(xù)維護(hù)性下降,因此需要一些其他工具來(lái)幫我們提高代碼質(zhì)量,減少一些不必要的錯(cuò)誤。本篇我們來(lái)介紹使用TS來(lái)做一些事情。
什么是TS
TypeScript 是微軟開(kāi)發(fā)一款開(kāi)源的編程語(yǔ)言,本質(zhì)上是向 JavaScript 增加靜態(tài)類型系統(tǒng)。它是 JavaScript 的超集,所有現(xiàn)有的 JavaScript 都可以不加改變就在其中使用。它是為大型軟件開(kāi)發(fā)而設(shè)計(jì)的,它最終編譯產(chǎn)生 JavaScript,所以可以運(yùn)行在瀏覽器、Node.js 等等的運(yùn)行時(shí)環(huán)境。
TS能做什么
首先TS的定位是靜態(tài)類型語(yǔ)言,而不是類型檢查器(對(duì)比f(wàn)low)。
從開(kāi)發(fā)工具提供的能力看也不僅僅是類型檢查,很直觀的就是Intellisense over Compilation Error,當(dāng)一段代碼有問(wèn)題(比如少寫(xiě)了字母)時(shí),寫(xiě)完馬上就會(huì)有紅色波浪線提示,而不是等到編譯的時(shí)候才告訴你哪一行有問(wèn)題。
因此使用TS提供的類型系統(tǒng)+靜態(tài)分析檢查+智能感知/提示,使大規(guī)模的應(yīng)用代碼質(zhì)量更高,運(yùn)行時(shí)bug更少,更方便維護(hù)。
對(duì)比js有哪些優(yōu)勢(shì)
- 開(kāi)發(fā)效率
雖然需要多寫(xiě)一些類型定義代碼,但TS在WebStorm等IDE下可以做到智能提示,智能感知bug,同時(shí)我們項(xiàng)目常用的一些第三方類庫(kù)框架都有TS類型聲明(@types管理),我們也可以給那些沒(méi)有TS類型聲明的穩(wěn)定模塊寫(xiě)聲明文件,這在團(tuán)隊(duì)協(xié)作項(xiàng)目中可以提升整體的開(kāi)發(fā)效率。
- 可維護(hù)性
長(zhǎng)期迭代維護(hù)的項(xiàng)目開(kāi)發(fā)和維護(hù)的成員會(huì)有很多,人員的不穩(wěn)定性和團(tuán)隊(duì)成員水平的差異的差異性,以及軟件本身具有熵的特質(zhì),導(dǎo)致長(zhǎng)期迭代維護(hù)的項(xiàng)目總會(huì)遇到可維護(hù)性逐漸降低的問(wèn)題。而有了強(qiáng)類型約束和靜態(tài)檢查,以及智能IDE的幫助下,可以降低軟件腐化的速度,提升可維護(hù)性。并且如果在重構(gòu)代碼時(shí),強(qiáng)類型和靜態(tài)類型檢查會(huì)幫上大忙,一定程度上減少重構(gòu)代價(jià)。(大家開(kāi)發(fā)維護(hù)起來(lái)更安全、放心)。
- 線上運(yùn)行質(zhì)量
我們現(xiàn)在的工具類項(xiàng)目很多bug都是由于一些調(diào)用方和被調(diào)用方的數(shù)據(jù)格式不匹配引起的。TS可以在編譯期進(jìn)行靜態(tài)檢查,可以在編寫(xiě)調(diào)試代碼時(shí)就發(fā)現(xiàn)這些問(wèn)題,并且IDE可以智能糾錯(cuò),編碼時(shí)就能提前感知bug的存在,我們的線上運(yùn)行時(shí)質(zhì)量會(huì)更為穩(wěn)定可控。
Flow、babel、tsc
- 類型檢查
flow用來(lái)做類型檢查,比如vue就是用的flow,但是flow也有很多問(wèn)題:
- 無(wú)用的錯(cuò)誤信息
比如 Incompatible instantiation for T, T 是一個(gè)類型變量,但是你并不能迅速找到這個(gè)錯(cuò)誤在哪里。
2.運(yùn)行困難
運(yùn)行 Flow是需要一定成本的。對(duì)于Mac 用戶來(lái)說(shuō)非常幸運(yùn),通過(guò) homebrew 可以安裝預(yù)制的二進(jìn)制包。但如果你需要自己編譯它,你就先得建立一套 OCaml 開(kāi)發(fā)環(huán)境。
- 代碼處理
babel相比于tsc,首先定位是不同的,babel是一種js預(yù)處理工具,理論上說(shuō)完全可以實(shí)現(xiàn)對(duì)ts的預(yù)處理,但是tsc對(duì)ts處理會(huì)更加精細(xì)。當(dāng)然tsc 的功能沒(méi)有 babel 多,擴(kuò)展性也沒(méi)有 babel 強(qiáng)。
項(xiàng)目的應(yīng)用
我們的開(kāi)源腳手架builder-webpack4已經(jīng)實(shí)踐了幾個(gè)月了,為了更好的維護(hù),我們決定遷移到ts。這中間踩了一些坑,但也積累了一些經(jīng)驗(yàn)。
- tsconfig配置
ts配置文件有很多配置項(xiàng),但是對(duì)于我們開(kāi)發(fā)node工具來(lái)說(shuō)其實(shí)用到的并不多,我們只需要關(guān)注模塊化,編譯路徑和輸出路徑即可。
關(guān)于模塊化,我們希望輸出的是commonjs規(guī)范的,至于最終是es5/es6或是其他,因人而異,我們需要配置的就是:
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es5",
"moduleResolution": "node"
}
編譯路徑和輸出路徑,這里跟webpack類似,當(dāng)然這里的編譯路徑是指定tsc編譯哪些目錄下的ts文件,否則編譯會(huì)因?yàn)閮?nèi)容太多而報(bào)錯(cuò)。
"compilerOptions": {
"outDir": "lib" //輸出路徑
},
//編譯目錄
"include": [
"src/**/*"
],
當(dāng)然我們還可能會(huì)指定types的路徑
"paths": {
"*": [
"node_modules/*",
"src/types/*"
]
}
- npm包的types
對(duì)于多數(shù)的npm包來(lái)說(shuō)都可以通過(guò)安裝@types/xxx來(lái)解決,比如node我們就可以安裝@types/node,當(dāng)然也有一些@types并沒(méi)有做管理,那就需要我們自己來(lái)寫(xiě)一下。舉個(gè)例子,webpack處理html相關(guān)會(huì)用到一個(gè)插件“html-webpack-plugin”,它是作為一個(gè)模塊來(lái)使用,那么只需要以下聲明即可
declare module 'html-webpack-plugin';
當(dāng)然你可能會(huì)用到某些UMD的包(既可以當(dāng)模塊又可以作為全局變量使用):
declare namespace UMD{
//可以定義一些其他東西
}
- interface(接口)
比如我們有些方法需要修改一些參數(shù),使用TypeScript 之后,把數(shù)據(jù)對(duì)應(yīng)的 interface 改掉,然后重新編譯一次,把編譯失敗的地方全部改掉就好了。
對(duì)于builder-webpack4來(lái)說(shuō)很多方法的參數(shù)都較為復(fù)雜,比如我們生成構(gòu)建配置文件的時(shí)候,webpack的配置老多了,自然是需要寫(xiě)個(gè)interface來(lái)控制,但是問(wèn)題是如果別的模塊調(diào)用這個(gè)方法又得重寫(xiě)一次?不用的,只要吧interface當(dāng)作模塊一樣導(dǎo)出即可(當(dāng)然你也可以把這些寫(xiě)到d.ts中,然后引入到對(duì)應(yīng)的文件)。
export interface xxx{
[propName: string]: any
}
- 靜態(tài)類型檢查
當(dāng)我們開(kāi)始盲寫(xiě)代碼的時(shí)候,總會(huì)不可避免的有些小失誤,那么利用IDE配合ts提供的工具就可以幫我們提前發(fā)現(xiàn)一些問(wèn)題;在編譯調(diào)試中同樣可以發(fā)現(xiàn)一些未觸及的點(diǎn)。

我們?cè)谡{(diào)用方法的時(shí)候就知道這個(gè)方法需要哪些參數(shù),當(dāng)然如果類型寫(xiě)錯(cuò)了就立馬會(huì)有紅色波浪線標(biāo)注出來(lái)(格外的扎眼)。

- 代碼質(zhì)量的提升
作為一種弱類型語(yǔ)言,js開(kāi)發(fā)一些大型/持續(xù)維護(hù)項(xiàng)目的時(shí)候,經(jīng)常會(huì)讓人體驗(yàn)什么是“開(kāi)發(fā)一時(shí)爽,重構(gòu)火葬場(chǎng)”(ts在Q你了)。
- 其他注意點(diǎn)
對(duì)于模塊的導(dǎo)出
export default builderWebpack4;
這個(gè)玩意編譯出來(lái)其實(shí)是這樣子的
exports.default = builderWebpack4;
但是對(duì)于調(diào)用者來(lái)說(shuō)并不能直接用,我們想要的是
module.exports = builderWebpack4;
所以源碼中多加個(gè)指定就好了
module.exports = exports.default;
當(dāng)然對(duì)于項(xiàng)目?jī)?nèi)部間模塊調(diào)用是不需要的,tsc構(gòu)建會(huì)生成相關(guān)的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"));
什么時(shí)候需要用
- 大型項(xiàng)目
項(xiàng)目越大,越難以維護(hù),因此對(duì)于多人寫(xiě)協(xié)作的大型項(xiàng)目有必要使用ts來(lái)提高代碼質(zhì)量。
- 持續(xù)維護(hù)的項(xiàng)目
對(duì)于一些長(zhǎng)久運(yùn)行的項(xiàng)目,既要保證它的穩(wěn)定運(yùn)行,又要保證項(xiàng)目交接便捷(可維護(hù)性),使用ts是目前來(lái)看最好選擇。
- 工具類項(xiàng)目
使用nodejs/js寫(xiě)一些前端工具或者庫(kù)的時(shí)候,同樣是需要關(guān)注以上兩點(diǎn)內(nèi)容,而且工具類的項(xiàng)目影響范圍較大,在開(kāi)發(fā)維護(hù)中要更加謹(jǐn)慎,那么使用ts幫我們盡量減少一些低級(jí)錯(cuò)誤是很有必要的。
寫(xiě)在最后
feflow的開(kāi)源腳手架builder-webpack4已經(jīng)切換為ts編寫(xiě)了,歡迎大家使用,如果有任何問(wèn)題可以提交issue。