@ts-check 立即上手,JSDoc 新增型別

elvinnn發表於2017-10-03

由於 JavsScript 是弱型別,所以在大型專案中使用時顯得能力略有不足。從七月份在騰訊實習到現在,接觸到了不少專案的程式碼,平均算來每天都有 70% 的時間用於閱讀、理解他人的程式碼。每次閱讀他人程式碼的時候,我心中都會冒出來兩個強烈的願望:要是 JavaScript是強型別的多好!要是文件能再詳細一點就好了!多虧了 TypeScript 和 JSDoc,這兩個願望都有變成現實的可能。

@ts-check 立即上手

使用 TypeScript 的最佳方式肯定是直接使用它的語法來編寫 .ts 檔案,然後通過編譯器轉換成 .js 檔案。然而對於老專案而言,切換構建往往意味著麻煩和巨大的風險,而且假如將來 JavaScript 也引入了型別系統(這非常有可能),那又得從 TypeScript 切回 JavaScript (迴歸標準)。那麼有沒有一種無痛的方式,讓我們既可以享受 TypeScript 帶來的好處,又能不改變專案的現有構建方式呢?

答案就是 // @ts-check,在 .js 檔案的頭部引入這樣一行註釋,就可以使用 TypeScript 了。

舉個例子,在下圖中我們首先宣告瞭一個變數 a,然後把數字 1 賦給了它,接著又把字串 '123' 賦給了它,看起來好像沒有什麼問題。

未使用 @ts-check
未使用 @ts-check

現在讓我們加上// @ts-check,咦,怎麼 a 下面出現了紅色的報錯?把滑鼠移到 a 處,發現報錯是"Type '"123'" is not assignable to type 'number'",也就是說在 TypeScript 中這種把字串 '123' 賦值給數字變數 a 的做法是不妥的。

使用 @ts-check
使用 @ts-check

享受 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。

參考連結

  1. Type Checking JavaScript Files
  2. JSDoc support in JavaScript

本文首發於elvin 的部落格

相關文章