TypeScriptによる型のある世界は生のJavaScriptでも体験できます。JSDocを使えば良いのです。JSDocを使えば型が付与された、しかし正真正銘のJavaScriptコードが書けます。JSDocはJavaScript実行環境にとってはただのコメントなので当然ブラウザでそのまま動きます。tsc
による変換の儀式は不要で、型チェックだけを行うことになります。
その型チェックもVSCodeを使っていればTypeScriptをインストールしなくても勝手にやってくれるので、小さなプロジェクトならこれで十分だと思います。VSCodeを使っている前提にはなりますが、その場合なんのセットアップや変換もなく型のあるJavaScriptを扱えます。
JSDocによる型付けは割と強力で一般的な場面ではほとんど不自由しません。しかし比較的よく使う型宣言なのに今まで唯一できないことがありました。それが関数のオーバーロードの宣言です。
そのJavaScriptでの関数のオーバーロード宣言はついにTypeScript5.0でできるようになる予定です。そのやり方を見ていきましょう。
基本
/**
* @overload
* @param {number} x
* @returns {'number'}
*/
/**
* @overload
* @param {string} x
* @returns {'string'}
*/
/**
* @overload
* @param {boolean} x
* @returns {'boolean'}
*/
/**
* @param {unknown} x
* @returns {string}
*/
function typeName(x) {
return typeof x;
}
(QiitaだとJSDocへのシンタックスハイライトがないので見づらいですね...VSCodeだと見やすいです。)
関数オーバーロード宣言のための新しいキーワード@overload
が追加されました。@overload
の下に@param
と@returns
で今まで通りの関数の型宣言をしたものを繰り返すことで複数のオーバーロードを宣言できます。一番下の@overload
がないコメントは関数の実装本体につけた型宣言ですね。つまり上のJSDocは以下のTypeScriptコードと同じです。
function typeName(x: number): 'number';
function typeName(x: string): 'string';
function typeName(x: boolean): 'boolean';
function typeName(x: unknown): string {
return typeof x;
}
先ほどの例では全てのオーバーロードを別のコメントに分けて書いていましたが、一つにして書くこともできます。
/**
* @overload
* @param {number} x
* @returns {'number'}
*
* @overload
* @param {string} x
* @returns {'string'}
*
* @overload
* @param {boolean} x
* @returns {'boolean'}
*
* @param {unknown} x
* @returns {string}
*/
function getTypeName(x) {
return typeof x;
}
@template
と同時に使う場合の注意
基本的に一つのコメントに書いた方が良さそうですが、@template
による型変数を使う場合は注意が必要なようです。型変数は一つのコメントごとがスコープになるので、同じ型変数名を別のオーバーロードでもう一度宣言することはできません。
/**
* @template T
* @template U
* @overload
* @param {T[]} array
* @param {(x: T) => U[]} iterable
* @returns {U[]}
*
* @overload
* @param {T[][]} array
* @returns {T[]}
*
* @param {unknown[]} array
* @param {(x: unknown) => unknown} iterable
* @returns {unknown[]}
*/
function flatMap(array, iterable = identity) {
/** @type {unknown[]} */
const result = [];
for (let i = 0; i < array.length; i += 1) {
result.push(.../** @type {unknown[]} */(iterable(array[i])));
}
return result;
}
または
/**
* @template T
* @template U
* @overload
* @param {T[]} array
* @param {(x: T) => U[]} iterable
* @returns {U[]}
*/
/**
* @template T
* @overload
* @param {T[][]} array
* @returns {T[]}
*/
/**
* @param {unknown[]} array
* @param {(x: unknown) => unknown} iterable
* @returns {unknown[]}
*/
function flatMap(array, iterable = identity) {
// 省略
}
はOK。以下はダメ。
/**
* @template T
* @template U
* @overload
* @param {T[]} array
* @param {(x: T) => U[]} iterable
* @returns {U[]}
*
* @template T
* @overload
* @param {T[][]} array
* @returns {T[]}
*
* @param {unknown[]} array
* @param {(x: unknown) => unknown} iterable
* @returns {unknown[]}
*/
function flatMap(array, iterable = identity) {
// 省略
}
型変数T
が同じコメント内で2回宣言されているのでダメになります。
あとがき
正真正銘のJavaScriptに型が付与される世界がどんどん豊かになってきて嬉しい。私の経験上、JSDocでの型付けしていて唯一まともな書き方で書けなかったのが関数のオーバーロードだった。リリースが待ち遠しい。