如何用 TypeScript 提高 JS 工程的健壯性?


瀉藥。。

@vilicvane 已經說的比較詳細了,這裡在補充一些小技巧。。

1. Typed Tuple

雖然 JavaScript 里一直沒有正宗的 Tuple 類型,但是在 JavaScript 中使用 Tuple 一直都是很常見的事情,可以基於數組也可以基於對象,有了解構賦值以後用數組還是對象的代碼量幾乎完全相同。

在 TypeScript ,如果我們需要一個具有靜態類型的 Tuple,一種方式是定義一個 interface 然後使用對象,但是這樣看起來比較麻煩。

另一種方式就是直接使用 TypeScript 提供的 Tuple 類型:

let error: [number, string] = [123, "Some Message"];

在 TypeScript 的類型標註中,當我們把類型寫在方括弧之前,就是 Typed Array,當類型寫在方括弧之中,就是 Typed Tuple 了。接著我們就可以使用類型安全的方式解構或者手動取值:

// Both correctly typed
let [code, message] = error; // code is number and message is string
let anotherCode = error[0]; // anotherCode is number
let anotherMessage = error[1]; // anotherMessage is string

2. String Literal Type

當我們需要使用靜態可枚舉的內容時,最正統的方式當然是使用 Enum。不過對於一些需要和已有 JavaScript 類庫交互時,必須得使用 String 類型,這時候往往會難以進行靜態檢查(例如拼寫錯誤)。

這時候,可以使用 TypeScript 提供的 String Literal Type:

class Socket {
on (event: "open" | "message" | "error" | "close", cb) {
// ...
}
}

let mySock = new Socket();
socket.on("message", () =&> {});

這樣,如果傳入的 String 不在預設範圍內,就會報錯,從而實現靜態檢查效果。

3. Void

對於所有沒有返回值的函數或者方法,都應該聲明為 Void 類型,而不是留空,後者為 Any 類型。

function log(message: string): void {
// ...
}

4. Implements Class

對於面向對象編程,組合優於繼承,但是對於組合,我們往往需要先定義一個介面類型,然後再定義一個這個介面的實現類型,十分繁瑣。

在 TypeScript 中,我們可以直接 Implements(而非 extends)一個(或多個)類:

class Base1 {
method1 () {}
}
class Base2 {
method2 () {}
}

class MyClass implements Base1, Base2 {
constructor (private base1: Base1, private base2: Base2) {}
method1 () { return this.base1.method1(); }
method2 () { return this.base2.method2(); }
}

這樣,我們就不要像 Java 那樣先定義一個 SomeInterface,再定義一個 SomeImpl,再把自己的 Class 實現 Interface,再把 SomeImpl 注進來那麼麻煩了。

效果上好像和 Intersection Type (T S)差不多?

5. //TODO

BTW。。上面某答主說因為 TypeScript 語法上是 JavaScript 的超集所以不算靜態類型的。。較為無語。。不寫類型 TypeScript 一樣有類型推理從而根據推理出的類型進行靜態檢查的,當然推理不是萬能的就是了。。然後告訴他 靜態/動態類型檢查 和語法沒有關係之後就被拉黑了。。

dynamic - What is the difference between a strongly typed language and a statically typed language?

terminology - Static/Dynamic vs Strong/Weak

雖然一個語言的類型系統多強才算強類型多弱算弱類型還有爭議,但是能把靜態/動態類型檢查和強/弱類型系統混淆這個並不是有爭議的內容,只是單純的

...In practice, it"s best to avoid the terms "strong" and "weak" altogether, because

  • Amateurs often conflate them with "static" and "dynamic".

...


謝邀.

這個問題感覺很難回答. 我大致說說我的一些個人習慣, 和代碼健壯性還是算稍有關聯.

  • 開啟 noImplicitAny, noImplicitReturns, 最新的版本里開啟 noImplicitThis, strictNullChecks.

  • 有同學已經提到, 少寫 any, 但這樣或許還不夠. 生產環境的代碼避免直接使用類型為 any 的值, 即使它被明確聲明為 any 類型. 在使用前可以進行一次類型轉換如: (value as Type).property; (map[index] as Type).property. 這個習慣可以確保引用查找和重命名的準確.
  • 與上一條對應, 在可能有多種類型時使用聯合類型 (Union Type), 方便 TypeScript 的類型收窄機制自動收窄到某一具體類型 (目前 TypeScript 不會對 any 類型進行收窄, 這一點可能未來會有變化). 如果自動的類型收窄不能完美處理, 處理方法同上.
  • 為所有方法顯示標註返回值類型.

其實 TypeScript 對能正常運行的代碼的健壯性有直接貢獻的地方我認為就是跟控制流相關的錯誤分析和類型收窄了. 但這一點一定程度上是需要和代碼書寫的靈活性平衡的. 當然最近 TypeScript 在這方面帶來了不少改進, 有興趣的同學可以上手玩玩看最新的 nightly build.

相關文章

TypeScript 類型收窄與基於控制流的類型分析 - JavaScript in TypeScript - 知乎專欄

TypeScript 中的 Non-nullable 類型 - JavaScript in TypeScript - 知乎專欄


謝邀,我就寫一些最佳體驗建議吧;

1、對於剛入門的同學,請單純的使用ts和js一比一的編譯模式(即一個ts文件編譯出同名的js文件,能不用tsconfig.json 配置文件就不用,因為對於大部分js開發場景這是最單純且不容易引入其它門檻的方法;

2、開始上正餐,強類型化你的AJAX介面,即為每一個AJAX請求的參數和返回類型做定製的interface,當然這也是有技巧的,同時也會影響你的服務端介面設計;

舉個例子,我們先假定你的服務API沒有用RESTful風格的設計(即在url中間帶參數,並且用很多的http method如Put、Delete),好好的單純用POST,只用POST,並且參數都是在Request Body里的JSON;

我們先建立一個webapi.d.ts的文件,把所有WebAPI的報文結構定義在這裡;

如圖,先定義一個interface來約束所有服務端介面返回的報文格式(是的,你的服務端最好都按這樣來開發);

接著我們用interface來規範你的分頁查詢參數和返回格式,是的,你的服務端也應該按照這個約定來開發;

然後我們接著設計兩個業務介面,一個是查詢實例,一個是查詢分頁數據,分別定義這2個參數的介面參數和返回數據類型, 好好觀察泛型T在這裡如何使用,是的要保證你的服務端妥妥的必須按照這個約定來開發;

第二個文件,http.ts, 這裡用到了API工廠方法設計,createAPI函數有兩個泛型模版TReq和TRes分別用來約束請求參數和返回類型,然後剛才我們定義的WebAPI的數據結構,在這裡被設計成兩個API的實現: getItemById和getItems,實際請求的功能使用jQuery實現的,這裡就不做細講了;

看起來代碼很複雜,其實編譯後的JS辣么簡單:

前面那些都是項目前期攢人品的體力活,到了產品中後期,以及維護階段,就是享受紅利了:

妥妥的請求參數提示:

返回數據類型提示:

完美的開發體驗,接著你只要回頭去大量往webapi.d.ts和http.ts 里搬磚(參照項目介面文檔),把業務介面全部定義出來,後面的開發工作就是虐菜了,重點可以放在提升交互體驗上;

再寫一點收工;

3、巧妙使用聯合類型;

JS開發中,我們經常需要對JSON對象進行屬性擴展,如果是強類型的就不方便了,在這裡使用中括弧肯定會被吐槽,可以試試隨寫隨用的聯合類型;

假定我們的業務場景里有一個列表視圖,然後要用一個欄位來表示某一個數據行被選中了,那麼我們就可以給原來的JSON結構擴展一個$checked屬性了(這裡加$只是我的命名習慣),上面兩種寫法都可以,TS的類型聲明表達式真是帥呆了,活用和|關鍵字可以寫出靈活又健壯的代碼,而這些類型代碼編譯到JS就只是空氣而已;雖然說js里給對象隨便加屬性很常見,不過開發一時爽,維護火葬場;先到這裡了,為什麼把api的數據結構專門放在d.ts文件里?因為可以跨項目引用啊,也許你的nodejs代碼也需要它,用js寫的手機App項目(Cordova/React Native)也需要它,反正d.ts運行時不載入的,真正做到一處編寫,到處使用,划算!


使用和優化上說不出太多,才接觸沒多久,寫Angular2項目時用過。

只是有一點,TS可以很好地建立Reusable Component,只要做好值的傳輸(input和output),就可以把每個組件寫活,進而在項目中重複利用。Angular1,我覺得provider可以、達到類似的效果,但其實模塊化做的遠不如用TS寫的Angular2項目好。個人感覺NG1的Provider其實只是方法或者邏輯的封裝,不涉及View層。而NG2在這方面可以說是一大突破。因此用TS寫的時候,最好多考慮考慮這一點。

一個朋友跟我說,現在的趨勢就是這樣:前端Reusable,後端Extendable。共勉


隨便談談想到的幾個點,想到哪裡寫到哪裡:

1.TypeScript最大的優勢在於能已很少的成本為你的javascript設計加上相當不錯的編譯時類型檢查。所以這個問題其實是個如何善用類型檢查的問題。

2.如其它答主所言,少用any,盡量聲明類型。到處都是any的typescript其實就是javascript

3.善用匿名類型。在javascript中我們常常會把一組變數用{}括成一個對象便於傳遞訪問,在Typescript里這種情況務必要為變數聲明加上匿名類型定義,如果用的地方多了還可以提出來聲明為一個介面。

4.別漏了聲明回調函數的簽名。TypeScript做編譯檢查的時候允許你忽略一部分回掉函數的傳入參數,所以使用強類型的回調函數不會影響你的代碼靈活性

5.為所有和服務端通信用到的plainobject定義介面類型。注意介面成員可以加上「?」來允許忽略,所以在這裡使用強類型這不會增加你的代碼量

6.使用老的javascript代碼或者外部javascript庫的時候一定要帶類型聲明。多數流行js庫都有typescript類型聲明文件,實在找不到或者是js代碼是自己的老代碼的話,自己寫類型聲明文件吧,反正也不麻煩,就寫自己用到的部分就好。

7.如果你是從C#/Java等強類型語言轉過來的,務必記得typescript是本質上是一門存粹的動態語言,別忘了享受動態語言的特性帶來的好處。


不要用[""]來訪問私有屬性或私有方法


把tslint報的錯都改正


推薦閱讀:

ui-route實現頁面跳轉回來頁面不重新載入?
如何評價 Node v6.0.0 (Current) ?
一些網站能自動添加 URL 到 Safari 的閱讀列表,這是如何實現的?
有哪些目前流行的前端框架?
ES6的class關鍵字有沒有實現真正的面向對象?

TAG:JavaScript | TypeScript |