由於 JavsScript 是弱型別,所以在大型專案中使用時顯得能力略有不足。從七月份在騰訊實習到現在,接觸到了不少專案的程式碼,平均算來每天都有 70% 的時間用於閱讀、理解他人的程式碼。每次閱讀他人程式碼的時候,我心中都會冒出來兩個強烈的願望:要是 JavaScript是強型別的多好!要是文件能再詳細一點就好了!多虧了 TypeScript 和 JSDoc,這兩個願望都有變成現實的可能。
@ts-check 立即上手
使用 TypeScript 的最佳方式肯定是直接使用它的語法來編寫 .ts 檔案,然後通過編譯器轉換成 .js 檔案。然而對於老專案而言,切換構建往往意味著麻煩和巨大的風險,而且假如將來 JavaScript 也引入了型別系統(這非常有可能),那又得從 TypeScript 切回 JavaScript (迴歸標準)。那麼有沒有一種無痛的方式,讓我們既可以享受 TypeScript 帶來的好處,又能不改變專案的現有構建方式呢?
答案就是 // @ts-check
,在 .js 檔案的頭部引入這樣一行註釋,就可以使用 TypeScript 了。
舉個例子,在下圖中我們首先宣告瞭一個變數 a,然後把數字 1 賦給了它,接著又把字串 '123' 賦給了它,看起來好像沒有什麼問題。
現在讓我們加上// @ts-check
,咦,怎麼 a 下面出現了紅色的報錯?把滑鼠移到 a 處,發現報錯是"Type '"123'" is not assignable to type 'number'",也就是說在 TypeScript 中這種把字串 '123' 賦值給數字變數 a 的做法是不妥的。
享受 TypeScript 型別系統的好處就是這麼簡單,不需要改變構建,不需要進行專案的遷移,所需要做的僅僅是在 .js 檔案的頭部加入 // @ts-check
(前提是你使用的是 VS Code,不過其它的編輯器下載相應的外掛即可)。
JSDoc 新增型別
如果僅僅使用 // @ts-check
的話,我們只能使用它的自動型別推斷功能,這對於大型專案來說是遠遠不夠的,我們希望能像強型別語言一樣指定每個變數的型別。本著不對專案產生侵入的原則,TypeScript 可以通過 JSDoc 風格的註釋來完成這一點。接下來的舉例說明取自官方的文件:
/**
* 使用 "@type" 來宣告型別
* @type {string}
*/
let var1;
/** @type {Window} */
let var2;
/**
* 用 “return” 說明函式的返回值型別
* @return {number}
*/
function fn1() {}
/**
* 可以像使用 "@return" 一樣使用 "@returns"
* @returns {{a: string, b: number}}
*/
function fn2() {}
/**
* 可以指定 union 型別,如字串或者布林值
* @type {(string | boolean)}
*/
let var3;
/**
* 宣告元素型別是數字的陣列 - 方式1
* @type {number[]}
*/
let var4;
/**
* 宣告元素型別是數字的陣列 - 方式2
* @type {Array.<number>}
*/
let var5;
/**
* 宣告元素型別是數字的陣列 - 方式3
* @type {Array<number>}
*/
let var6;
/**
* 宣告物件型別
* @type {{a: string, b: number}}
*/
let var7;
/**
* 用 "@typedef" 自定義複雜型別
* @typedef {Object} SpecialType - 建立一個新的型別 'SpecialType'
* @property {string} prop1 - SpecialType 屬性 prop1 是 string 型別
* @property {number} prop2 - SpecialType 屬性 prop2 是 number 型別
* @property {number=} prop3 - SpecialType 屬性 prop3 是可選的 number 型別
* @prop {number} [prop4] - SpecialType 屬性 prop4 是可選的 number 型別
* @prop {number} [prop5=42] - SpecialType 屬性 prop5 是可選的 number 型別(預設值 42))
*/
/** @type {SpecialType} */
let specialTypeObject;
/**
* 宣告函式引數型別
* @param p0 {string} - TS 風格宣告 p0
* @param {string} p1 - p1 是 string 型別引數
* @param {string=} p2 - p2 是可選的 string 型別引數
* @param {string} [p3] - 另外一種可選引數寫法
* @param {string} [p4="test"] - p4 是可選的 string 型別引數(預設值為 "test")
* @return {string} - 函式返回值是 string 型別
*/
function fn3(p0, p1, p2, p3, p4){
// TODO
}
/**
* 也可以使用模板來宣告型別
* 如 fn4 表示返回值和引數 p1 是相同型別
* @template T
* @param {T} p1
* @return {T}
*/
function fn4(p1){}複製程式碼
寫在最後
對於老專案,使用 // @ts-check
和 JSDoc 引入 TypeScript 來享受型別系統的好處是最簡單、學習成本最低的方法。對於新專案,相較於激進地使用 .ts 檔案,我認為 // @ts-check
和 JSDoc 是更好的方法,因為 JavaScript 在不久的未來很有可能會引入可選的型別系統(類似於Python 3),到時候可以避免再從 TypeScript 迴歸 JavaScript。
參考連結
本文首發於elvin 的部落格。