沒辦法用 TypeScript?別擔心,你還可以寫 JSDoc 標注類型

JavaScript

前言

如果你的公司專案沒辦法使用 TypeScript 的話,但又希望可以有類似 TypeScript 的類型檢查,那麼你可以試試看 JSDoc 來標注類型,這樣就可以有類似 TypeScript 的類型檢查了。

JSDoc 是什麼?

首先先聊一下 JSDoc 是什麼東西,JSDoc 全名是「JavaScript Documentation」,中文翻譯是「JavaScript 文件」,它是一個專門用來標註 JavaScript 文件的工具。

那它可以做什麼呢?其實它可以幫助你標註類型,例如:型別、參數與回傳值等等,因此在還沒有 TypeScript 之前,JSDoc 會是一個非常好用的工具,因為已經足夠堪用了。

那麼接下來我們就準備來看看 JSDoc 該怎麼使用吧!但是在開始之前我想先說明一下我的 IDE,也就是 Visual Studio Code,身為地表上最強大最好用的 IDE,它對於 JSDoc 的支援也是非常的好,因此我會以 Visual Studio Code 來做為示範。

Note
JSDoc 其實很早就出現了,約 1999 年左右,所以其實比 TypeScript 還要早出來唷。

準備動作

首先我們先使用 VSCode 建立一個專案叫做 jsdoc-demo,並且請在該專案初始化 npm

1
npm init -y

接著建立一個 index.js 檔案,所以你目前的專案結構應該會長這樣

init

準備好差不多之後,我們就繼續往下吧。

JSDoc 的撰寫方式

JSDoc 的撰寫方式並不困難,首先我這邊先寫一段範例程式碼

1
2
3
const sayHi = (name) => {
return `Hi, ${name}`;
};

接下來該怎麼撰寫呢?你只需要在該函式的上方加上 /**,我們強大的 VSCode 就會提示你 JSDoc 註解

JSDoc 註解

此時你只需要按下 Enter 鍵,就會自動幫你產生 JSDoc 註解了,如下圖

JSDoc 註解

是不是超級方便呢?那麼這邊我們可以看到 VSCode 自動幫我們產生了 @param {*} name@returns,那這邊我們該怎麼撰寫呢?其實很簡單,通常我們會去描述該函式的功能、參數的型別以及回傳值的型別,所以會這樣子撰寫

1
2
3
4
5
6
7
8
/**
* 打招呼函式
* @param { string } name 名字
* @returns { string } Hi, ${name}
*/
const sayHi = (name) => {
return `Hi, ${name}`;
};

接下來當你滑鼠只要移動過去 sayHi 這個函式,就會出現提示訊息

sayHi

因此當如果你在其他地方使用到該函式的時候,就可以透過 JSDoc 來提示你該函式的功能、參數的型別以及回傳值的型別,大大減少你還要去看函式是幹嘛的、該傳什麼參數以及回傳什麼值的時間。

當然 JSDoc 不只有這樣,你也可以使用 @example 來描述該函式的使用方式

1
2
3
4
5
6
7
8
9
10
/**
* 打招呼函式
* @param { string } name 名字
* @returns { string } Hi, ${name}
* @example
* sayHi('John'); // Hi, John
*/
const sayHi = (name) => {
return `Hi, ${name}`;
};

@example

那麼如果是可以接受多個型別呢?例如:string 或是 number,那麼你可以這樣寫

1
2
3
4
5
6
7
8
9
10
/**
* 打招呼函式
* @param { string | number } name 名字
* @returns { string } Hi, ${name}
* @example
* sayHi('John'); // Hi, John
*/
const sayHi = (name) => {
return `Hi, ${name}`;
};

type

那麼這邊也來提一件事情,為什麼會說如果不能使用 TypeScript 的話,JSDoc 也是一個不錯的選擇呢?透過上面示範我們可以知道 JSDoc 確實是可以幫助到我們一些問題,但是這樣子遠遠是不夠的,因為開發者是人,所以可能 sayHi 我們就已經寫了 @param { string } name 名字,但是我們還是不小心在呼叫 sayHi 的時候傳入了 number

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 打招呼函式
* @param { string } name 名字
* @returns { string } Hi, ${name}
* @example
* sayHi('John'); // Hi, John
*/
const sayHi = (name) => {
return `Hi, ${name}`;
};

sayHi(1);

那這樣子就會造成錯誤,對於我們開發者來講,是有可能發生一些 Bug 的,所以就只能認命使用 TypeScript 嗎?不,其實 JSDoc 也可以做到型別驗證的,只是目前我們的環境還沒有設定好,所以我們先來設定一下。

JSDoc 類型檢查和自動完成

首先我們在專案根目錄建立一個 jsconfig.json 檔案,並且輸入以下內容

1
2
3
4
5
6
7
{
"compilerOptions": {
"checkJs": true,
"allowJs": true
},
"exclude": ["node_modules"]
}

接著你應該就可以看到錯誤提示訊息囉

JSDoc 型別驗證

而且如果你沒有正確傳入參數的話,也會有提示訊息

JSDoc 參數驗證

如果你是 TypeScript 的開發者應該會覺得 jsconfig.json 很熟悉,因為 jsconfig.js 是由 tsconfig.js 所衍生出來的,所以你可以在 jsconfig.js 中使用 tsconfig.js 的設定,例如:"target": "es6""module": "commonjs" 等等,這邊我們就不多做介紹,如果你想要了解更多,可以參考官方文件

JSDoc 其他寫法

講完了前面 JSDoc 的基本寫法跟基本設置後,我們繼續來聊一下 JSDoc 的其他寫法,以實戰開發來講,我們不可能這麼單純,畢竟還會有陣列跟物件等狀況,所以一開始我們先以一個單純的變數宣告來講,如果要替變數宣告上型別的話,那就會使用到新的 JSDoc 語法,也就是 @type,底下我也列出一些示範寫法

變數宣告

1
2
3
4
5
/**
* 我的名字
* @type { string }
*/
const myName = 'Ray';

型別

透過定義變數其實也可以像 TypeScript 讓下拉選取變得很正確

下拉選取

單型別陣列

1
2
3
4
5
/**
* 我的名字
* @type { string[] }
*/
const myName = ['Ray', 'John'];

單一型別陣列

多型別陣列

1
2
3
4
5
/**
* 我的名字
* @type { (string | number)[] }
*/
const myName = ['Ray', 'John', 1];

多型別陣列

物件

1
2
3
4
5
6
7
8
/**
* 我的名字
* @type { { name: string, age: number } }
*/
const myName = {
name: 'Ray',
age: 18,
};

物件

如果你先寫 JSDoc 再去寫物件屬性時,當你屬性沒有對上 JSDoc 也會出現警告

物件

函式

1
2
3
4
5
6
7
8
9
10
/**
* 打招呼函式
* @param { string } name 名字
* @returns { string } Hi, ${name}
* @example
* sayHi('John'); // Hi, John
*/
const sayHi = (name) => {
return `Hi, ${name}`;
};

上面是我們前面所示範的函式,但是我們實戰開發上是很長傳入物件的,所以我們來看一下物件的寫法

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 打招呼函式
* @param { object } options 選項
* @param { string } options.name 名字
* @param { number } options.age 年齡
* @returns { string } Hi, ${name}
* @example
* sayHi({ name: 'John', age: 18 }); // Hi, John
*/
const sayHi = ({ name, age }) => {
return `Hi, ${name},今年 ${age} 歲`;
};

物件

當然也是一樣可以驗證的

驗證

如果是函式的話呢?其實也很簡單

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 打招呼函式
* @param { Function } callback 回呼函式
* @returns { string } Hi, ${name}
* @example
* sayHi(() => {}); // Hi, John
*/
const sayHi = (callback) => {
return `Hi, ${callback()}`;
};

sayHi(() => 'John');

函式

如果函式沒打算回傳值的話,那就可以使用 void 來定義

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 打招呼函式
* @param { Function } callback 回呼函式
* @returns { void }
* @example
* sayHi(() => {}); // Hi, John
*/
const sayHi = (callback) => {
console.log(`Hi, ${callback()}`);
};

sayHi(() => 'John');

JSDoc 進階寫法 - @typedef

當我們型別比較複雜時,我們是可以使用 @typedef 來定義型別的,這樣就不用一直重複寫了,例如:我們要定義一個物件型別,裡面有 nameage 兩個屬性,這時候我們就可以使用 @typedef 來定義

1
2
3
4
5
/**
* @typedef { object } User 選項
* @property { string } name 名字
* @property { number } age 年齡
*/

通常 @typedef 會放在檔案的最上面,因此就會像這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* @typedef { object } User 選項
* @property { string } name 名字
* @property { number } age 年齡
*/

/**
* 打招呼函式
* @param { User } user 使用者資料
* @returns { string } Hi, ${name}
* @example
* sayHi({ name: 'John', age: 18 }); // Hi, John
*/
const sayHiFn = ({ name, age }) => {
return `Hi, ${name},今年 ${age} 歲`;
};

/**
* 使用者資料
* @type { User }
*/
const user = {
name: 'Ray',
age: 18,
};

sayHiFn(user)

同樣的,如果你傳入錯誤的型別,也會出現警告

錯誤

那麼如果我們定義的 @typedef 需要跨檔案使用時,我們可以使用 @typedef@global 來定義,這樣就可以跨檔案使用了

1
2
3
4
5
6
/**
* @typedef { object } User 選項
* @property { string } name 名字
* @property { number } age 年齡
* @global
*/

接下來你就可以在其他檔案使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 打招呼函式
* @param { User } user 使用者資料
* @returns { string } Hi, ${name}
* @example
* sayHi({ name: 'John', age: 18 }); // Hi, John
*/
const sayHiFn = ({ name, age }) => {
return `Hi, ${name},今年 ${age} 歲`;
};

/**
* 使用者資料
* @type { User }
*/
const user = {
name: 'Ray',
age: 18,
};

sayHiFn(user)

通常會把 @typedef 定義在 types.js 檔案中,所以會建立一個資料夾叫做 types,然後在裡面建立 index.js 檔案,內容如下

1
2
3
4
5
6
/**
* @typedef { object } User 選項
* @property { string } name 名字
* @property { number } age 年齡
* @global
*/

JSDoc 常見語法總結

最後就差不多要來準備下個結論,也就是上面文章中有使用到的 JSDoc 語法

  • @param:函式參數
  • @returns:函式回傳值
  • @example:使用範例
  • @typedef:自定義型別
  • @global:跨檔案使用 @typedef
  • @property:物件屬性
  • @type:型別

當然不只有這些,所以如果你想要更深入了解的話,我會建議你直接看 JSDoc 官方文件,因為 JSDoc 語法真的非常多,不太可能每一個都介紹到,所以我只介紹了常用的,如果你想要更深入了解的話,可以直接看官方文件哩~

結論

其實我們可以發現就算沒有使用 TypeScript,也可以達到類似 TypeScript 的型別驗證,最後這邊我也將上面範例檔案整理了一下,以便你可以搭配這一篇文章來看

基本上裡面我都有寫一些範例了,應該是可以讓你更了解 JSDoc 的使用方式哩

Liker 讚賞

這篇文章如果對你有幫助,你可以花 30 秒登入 LikeCoin 並點擊下方拍手按鈕(最多五下)免費支持與牡蠣鼓勵我。
或者你可以也可以請我「喝一杯咖啡(Donate)」。

Buy Me A Coffee Buy Me A Coffee

Google AD

撰寫一篇文章其實真的很花時間,如果你願意「關閉 Adblock (廣告阻擋器)」來支持我的話,我會非常感謝你 ヽ(・∀・)ノ