LoginSignup
4
2

More than 1 year has passed since last update.

ジェネリクスと合わせるにはデフォルト引数より省略可能引数

Posted at

TypeScriptのジェネリクスをデフォルト引数と組み合わせようとしたら、型が合わない事態に陥ってしまいました。

事例

「何かしらの変換を行う機能はあるけど、省略すれば何も行わない」ような挙動をさせたい場所はちょくちょくあるかと思いますが、そのような状況でデフォルト引数を使ってハマってしまいました。

ここでは、1つの例としてprocessArray(arr, [func])というような関数を考えてみます。arrには配列を、funcには要素を変換する関数を必要なら渡す、Array.fromの劣化コピーのような関数です。

デフォルト引数で失敗

ここで、実装の手間を省くために、引数の関数が渡されないときは恒等関数(x => x)で処理させることにしたとします。デフォルト引数にこれを入れてTypeScriptにしてみます。

const identity = <T,>(x: T) => x;

const processArray = <T, U>(arr: readonly T[], func: (val: T) => U = identity): U[] => {
    return arr.map(func);
}

これはエラーになってしまいます(Type '(x: T) => T' is not assignable to type '(val: T) => U'.
Type 'T' is not assignable to type 'U'.
'U' could be instantiated with an arbitrary type which could be unrelated to 'T'.)。tsc曰く、

Type '<T>(x: T) => T' is not assignable to type '(val: T) => U'.
  Type 'T' is not assignable to type 'U'.
    'U' could be instantiated with an arbitrary type which could be unrelated to 'T'.

とのことで、デフォルト引数が適用された場合についてUの型を制御する、というのはできないようです。

省略可能な引数にしてみる

一方で、funcを省略可能な引数で書く方法も考えられます。

const identity = <T,>(x: T) => x;

const processArray = <T, U = T>(arr: readonly T[], func?: (val: T) => U): U[] => {
    return arr.map((func || identity) as (val: T) => U);
}

こちらは成功します。

結論

ジェネリックな引数にデフォルト引数を設定しようとすると、うまく型が付かない。

4
2
1

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
4
2