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