LoginSignup
6
7

More than 1 year has passed since last update.

JavaScriptで関数のオーバーロードを宣言する【TypeScript5.0新機能】

Last updated at Posted at 2023-02-09

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による型変数を使う場合は注意が必要なようです。型変数は一つのコメントごとがスコープになるので、同じ型変数名を別のオーバーロードでもう一度宣言することはできません。

OK
/**
 * @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;
}

または

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) {
  // 省略
}

は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での型付けしていて唯一まともな書き方で書けなかったのが関数のオーバーロードだった。リリースが待ち遠しい。

6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7