Lyft的TypeScript实践
來(lái)自Lyft的前端工程師Mohsen Azimi介紹了Lyft向TypeScript轉(zhuǎn)型的過(guò)程,說(shuō)明JavaScript類型系統(tǒng)的重要性、為什么Lyft選擇TypeScript以及他們的一些實(shí)踐經(jīng)驗(yàn)。以下內(nèi)容翻譯自作者的博客,查看原文TypeScript at Lyft。
在我剛剛成為JavaScript開發(fā)者的時(shí)候,當(dāng)有人說(shuō)要給JavaScript加入類型系統(tǒng),我就會(huì)問(wèn)自己:為什么要這么做?
現(xiàn)在,我已經(jīng)成為一個(gè)JavaScript老手,我難以想象沒(méi)有類型系統(tǒng)支持的JavaScript會(huì)是怎樣的。大型的JavaScript應(yīng)用需要類型信息來(lái)提升擴(kuò)展性和可維護(hù)性,Lyft的很多JavaScript項(xiàng)目(從Lyft.com網(wǎng)站到我們的內(nèi)部工具)也不例外。當(dāng)我還是個(gè)JavaScript狂熱者的時(shí)候,看著Lyft的團(tuán)隊(duì)和代碼庫(kù)在膨脹,開始意識(shí)到純粹的JavaScript已經(jīng)無(wú)法支撐起大型的應(yīng)用了。
但這也并非意味著要扔掉JavaScript。如果能夠加入類型系統(tǒng),一切都會(huì)變得不一樣。類型系統(tǒng)可以減少bug,開發(fā)者也因此能夠更加方便地查看代碼。下面我將講述Lyft為什么選擇了TypeScript以及是怎么做到的。
Bug!
“Uncaught TypeError: Cannot read property "foo" of undefined”是JavaScript最常見的一個(gè)錯(cuò)誤。在訪問(wèn)一個(gè)未定義(undefined)的引用對(duì)象的屬性時(shí)就會(huì)發(fā)生這個(gè)錯(cuò)誤。Lyft的純JavaScript項(xiàng)目在生產(chǎn)環(huán)境經(jīng)常會(huì)出現(xiàn)這個(gè)問(wèn)題。JavaScript的常見錯(cuò)誤還包括拼寫錯(cuò)誤,比如“document.getElementbyId”,這類錯(cuò)誤在生產(chǎn)環(huán)境會(huì)引發(fā)大問(wèn)題。
REST API的類型不匹配問(wèn)題雖然不常見,但一旦發(fā)生就是個(gè)大bug。比如,API響應(yīng)消息里的一個(gè)字段從數(shù)字類型變成字符串類型,會(huì)導(dǎo)致JavaScript出現(xiàn)不可預(yù)期的行為。
開發(fā)效率
瀏覽大型的JavaScript代碼庫(kù)不僅耗時(shí)而且容易讓人感到困惑,找不到函數(shù)的定義或搞不清楚函數(shù)可以接收哪些參數(shù)都是常有的事。以下列這段代碼為例:
/** * Create an input element * @param {string} type * @param {string|boolean} value * @return {HTMLInputElement} */ function createInput(type, value) {const el = document.createElement('input');el.type = type;if (type === 'checkbox') {el.checked = value;} else {el.value = value;}return el; }這個(gè)函數(shù)根據(jù)傳入的類型返回一個(gè)HTMLInputElement對(duì)象,如果是復(fù)選框類型,就把復(fù)選框的“checked”屬性值設(shè)置為傳入的值,否則的話就創(chuàng)建一個(gè)輸入框,并把輸入框的值設(shè)置為傳入的值。
這里可能會(huì)出現(xiàn)bug,假設(shè)在創(chuàng)建復(fù)選框時(shí)傳入了錯(cuò)誤的值,比如:
const input = createInput('checkbox', 'false')即使代碼注釋里寫得很清楚,仍然無(wú)法阻止bug的產(chǎn)生。把字符串“false”賦值給復(fù)選框,但復(fù)選框的狀態(tài)仍然會(huì)是“true”,因?yàn)樽址癴alse”會(huì)被解析成布爾類型的“true”。
而如果有了類型系統(tǒng),就可以通過(guò)重載幫助開發(fā)人員寫出正確的代碼:
/** * Create an input element */ function createInput(type: 'text', value: string): HTMLInputElement; function createInput(type: 'checkbox', value: boolean): HTMLInputElement; function createInput(type, value) {// code }類型系統(tǒng)讓代碼變得更強(qiáng)大,通過(guò)API級(jí)別的語(yǔ)義可以在一開始就把bug扼殺在襁褓里。
類型系統(tǒng)讓重構(gòu)變得更簡(jiǎn)單,開發(fā)人員也因此可以放心地做出代碼變更。例如,當(dāng)一個(gè)函數(shù)簽名發(fā)生變化,在調(diào)用方代碼沒(méi)有做出相應(yīng)改動(dòng)之前是無(wú)法通過(guò)TypeScript編譯的。
強(qiáng)類型的代碼之所以更容易進(jìn)行重構(gòu),是因?yàn)轭愋蜋z查器可以確保代碼變更可以與項(xiàng)目的其他部分兼容。IDE或代碼編輯器為類型系統(tǒng)提供了支持。
帶有類型信息的模塊更容易維護(hù)。使用TypeScript開發(fā)的模塊在一些編輯器里可以顯示出API的提示信息。
TypeScript與FlowType的對(duì)決
我們有多種JavaScript類型系統(tǒng)可選擇:
-
帶有JSDoc類型注解的Google Closure編譯器
-
FlowType
-
TypeScript
雖然我們最終選擇了TypeScript,但做出這個(gè)決定也并不是那么容易的。我們的團(tuán)隊(duì)分成兩個(gè)陣營(yíng),一個(gè)傾向于選擇FlowType,一個(gè)傾向于選擇TypeScript。
“FlowType就是JavaScript”
FlowType被認(rèn)為“就是JavaScript”或者“帶有類型注解的JavaScript”。這種看法有失偏頗。FlowType是一門獨(dú)立的語(yǔ)言,它的語(yǔ)法是JavaScript的超集。它使用了.js作為文件擴(kuò)展名,所以導(dǎo)致了人們的混淆。實(shí)際上,不管是JSX還是FlowType,它們都不是JavaScript。同樣,TypeScript和ECMAScript也不是。
調(diào)用方類型檢查
調(diào)用方類型檢查是FlowType的一個(gè)非常受歡迎的特性。比如:
function power2(a) {return a * a; } power2('string')這個(gè)函數(shù)接收一個(gè)數(shù)字類型的參數(shù),如果在調(diào)用時(shí)傳入了一個(gè)字符串,FlowType會(huì)對(duì)此作出警告。而TypeScript則會(huì)認(rèn)為“a”是任意類型,所以可以通過(guò)編譯。
這個(gè)特性看起來(lái)令人印象深刻,但我們只要對(duì)代碼稍作改動(dòng),這個(gè)特性就不管用了。比如:
function foo(a) {console.log(a.b) } foo({})這段代碼可以通過(guò)FlowType的編譯。
React
因?yàn)镽eact和FlowType都是由Facebook開源的,看似FlowType比TypeScript更適合用在React中。但在Lyft的項(xiàng)目中,我們并沒(méi)有發(fā)現(xiàn)把這兩者用在React中有什么不同。
流行程度
雖說(shuō)FlowType和TypeScript看似旗鼓相當(dāng),但出于對(duì)生態(tài)系統(tǒng)未來(lái)發(fā)展的考慮,我們需要關(guān)注它們的流行程度。選擇流行程度較高的那一個(gè)可以幫助Lyft吸引到更多的開發(fā)人才。
要衡量一個(gè)開源項(xiàng)目的流行程度并非易事,不過(guò)我還是試著努力找出它們之間的對(duì)比數(shù)據(jù)。
StackOverflow上的問(wèn)題數(shù)量:FlowType——900多個(gè);TypeScript——38,000多個(gè)。
GitHub上的問(wèn)題數(shù)量:FlowType——1500多個(gè)未解決,2200個(gè)已關(guān)閉;TypeScript——2400多個(gè)未解決,11,200個(gè)已關(guān)閉。
GitHub上的拉取請(qǐng)求:FlowType——60多個(gè)未解決,1,200個(gè)已關(guān)閉;TypeScript——100多個(gè)未解決,5000多個(gè)已關(guān)閉。
npm每月下載數(shù)量:FlowType——290多萬(wàn)次;TypeScript——720多萬(wàn)次。
外部類型定義數(shù)量:FlowType——340多個(gè),在GitHub上有43000個(gè)“流類型”目錄,有些庫(kù)還提供了.flow類型定義;TypeScript——3700多個(gè),在GitHub上有約25萬(wàn)個(gè)package.json里包含了類型定義,Facebook的Redux和ImmutableJS也提供了TypeScript類型定義。
我們?cè)趦?nèi)部進(jìn)行了一個(gè)問(wèn)卷調(diào)查,與上述的數(shù)據(jù)一樣,TypeScript在Lyft內(nèi)部也很受歡迎。
遷移到TypeScript
我們的項(xiàng)目里有大量的純JavaScript代碼,要一下子把它們?nèi)哭D(zhuǎn)成TypeScript并非明智的做法。
于是,我們選擇了增量遷移。我們使用Webpack編譯我們的前端應(yīng)用,通過(guò)TypeScript-loader可以很輕松地將TypeScript引入到Webpack中。有了TypeScript-loader,我們就可以一邊使用TypeScript編寫新代碼,一邊零碎地更新舊代碼。
TypeScript編譯器可以對(duì)JavaScript文件進(jìn)行類型檢查,所以我們就利用了這一特性對(duì)已有的JavaScript代碼進(jìn)行類型錯(cuò)誤檢查。當(dāng)然,這個(gè)只對(duì)直接被導(dǎo)入到TypeScript中的JavaScript文件有效。不過(guò),最新版本(2.5)的編譯器幾乎可以直接用于檢查獨(dú)立的JavaScript文件。
推廣TypeScript
網(wǎng)上有很多TypeScript的學(xué)習(xí)資源。TypeScript的官方網(wǎng)站就提供了大量的學(xué)習(xí)資料,方便開發(fā)者入門。另外,TypeScript的語(yǔ)法是JavaScript的超集,所以對(duì)于前端開發(fā)人員來(lái)說(shuō)非常直觀。
lint工具不僅有助于開發(fā)者學(xué)習(xí)TypeScript,對(duì)寫出一致、流暢的代碼也很有幫助。lint工具在沒(méi)有類型系統(tǒng)的情況下有助于減少有問(wèn)題的代碼,而在有類型系統(tǒng)的情況下就更是能夠起到保護(hù)代碼的作用,所以我們?cè)谒械捻?xiàng)目里使用了TSLint。TSLint比一般的lint工具更強(qiáng)大,它可以捕捉到一些很意思的問(wèn)題,比如awaiting-noon-promise,而這在純JavaScript的lint工具里是做不到的。
在進(jìn)行TypeScript培訓(xùn)和往我們的代碼庫(kù)引入TypeScript的過(guò)程中,我們發(fā)現(xiàn)我們的很多JavaScript代碼無(wú)法直接使用類型。雖然我們因此感到沮喪,但這也正好說(shuō)明了我們的代碼寫得不好,需要進(jìn)行重構(gòu)。
Lyft的TypeScript實(shí)踐
既然引入了TypeScript,Lyft的很多新項(xiàng)目就是純TypeScript的了。以下是一些使用了TypeScript的項(xiàng)目示例。
TypeScript React轉(zhuǎn)換器
React為它的組件提供了基于運(yùn)行時(shí)的類型系統(tǒng)——PropTypes。我們?cè)贚yft的非TypeScript項(xiàng)目里重度使用了PropTypes。
但在TypeScript里,已經(jīng)不需要運(yùn)行時(shí)類型檢查了,所有的類型檢查都發(fā)生在編譯階段。于是,我們花了一些時(shí)間開發(fā)了React JavaScript-to-TypeScript轉(zhuǎn)換器。它利用TypeScript編譯器將React組件里的PropTypes轉(zhuǎn)成TypeScript接口。這個(gè)工具加速了我們采用TypeScript的進(jìn)程,為我們節(jié)省了大量的時(shí)間。
通用異步組件
我們嘗試在服務(wù)器端動(dòng)態(tài)渲染加載的組件,這個(gè)項(xiàng)目完全使用TypeScript開發(fā),它也很好地展示了如何利用類型系統(tǒng)寫出可共享的代碼。
我們的CSS框架與TypeScript集成
在Lyft,我們使用了一個(gè)叫作Tetris的原子CSS庫(kù)。原子CSS的核心理念是說(shuō),重復(fù)類名比重復(fù)CSS代碼的代價(jià)更小。原子CSS框架提供了很多小型的CSS類,可以通過(guò)組合它們來(lái)給元素添加樣式。在使用Tetris時(shí),開發(fā)人員需要記住大量CSS類名(比如p-a-m,用于給方框增加填充空間)。
為了提升開發(fā)效率,我們編寫了一個(gè)TypeScript文件,它把所有的Tetris類名都導(dǎo)出來(lái),開發(fā)人員可以在他們最喜歡的編輯器里導(dǎo)入這個(gè)文件,然后就可以使用類名自動(dòng)完成功能了。
Swagger到TypeScript代碼生成
與后端API集成的代碼經(jīng)常會(huì)出現(xiàn)bug,這是最讓人頭痛的問(wèn)題。后端的變更會(huì)影響到前端代碼,又或者有時(shí)候前端的代碼變更會(huì)破壞與后端API的兼容性。
我們的后端API是通過(guò)OpenAPI(Swagger 2.0)來(lái)描述的,我們因此能夠規(guī)范前端應(yīng)用的API使用。我們使用Swagger JSON Schema模型為API客戶端自動(dòng)生成TypeScript接口。
原文地址:http://www.infoq.com/cn/news/2017/10/TypeScript-practice-Lyft
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)
總結(jié)
以上是生活随笔為你收集整理的Lyft的TypeScript实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用acs-engine在Azure中国
- 下一篇: 通过Swashbukle给DotNet