下記のような場合に<T extends unknown>(arg: T[]) => T[]
と定義した場合だけ、TypeScriptのコンパイル時にエラーが発生して🤔となったけど納得したのでMEMOしておきます📝
type TypeA = <T extends unknown>(arg: T[]) => T[] type TypeB = <T extends unknown[]>(arg: T) => T const funcA: a = (...args) => args const funcB: b = (...args) => args funcA("aaa", 1) // 型 'number' の引数を型 'string' のパラメーターに割り当てることはできません。ts(2345) funcB("aaa", 2)
検証した環境は下記の通りです。
$ npx tsc -v Version 3.9.7
個人的には最初は同じかと思っていたのですが、、、TypeScriptは実際には下記ような形で型を設定していました。
const funcA: <string>(...args: string[]) => string[] const funcB: <[string, number]>(args_0: string, args_1: number) => [string, number]
エラーが発生していたfuncA
は...args: string[]
となっているためfuncA("aaa", 1)
でnumber
型の引数1
を渡しているため型エラーが発生していたようです。
流れとしてはジェネリック型は呼び出し時に動的に型が決まります。
funcA
の場合は<T extends unknown>
としており、funcA("aaa", 1)
の最初の引数の型がstring
のためジェネリック型T
がstring
に置き換わり、string[]
型が指定されてconst funcA: <string>(...args: string[]) => string[]
となっているようです👀
// 最初の定義 const funcA: <T extends unknown>(...args: T[]) => T[] // funcA("aaa", 1)の呼び出しによって第一引数の型がTに反映される const funcA: <string>(...args: string[]) => string[]
ジェネリック型のT
が配列として定義されていないので最初に合致したstring
型に置き換わってしまうのがポイントっぽいですね。
逆にfuncB
の場合は<T extends unknown[]>
としているので可変長引数がタプルとして扱い、そのままT
が[string, number]
に置き換わっているのでエラーにならないようです👀
// 最初の定義 const funcB: <T extends unknown[]>(arg: T) => T // funcB("aaa", 1)の呼び出しによって引数のタプルがTに反映される const funcB: <[string, number]>(args_0: string, args_1: number) => [string, number]
どうやらジェネリック型の宣言で指定したものと合致するように解釈して型を推論してくれているみたいですね。
一応、funcA
も下記のように明示的に指定してあげると(string|number)[]
と判断してくれてエラーは発生しなくなりました😅
funcA<string | number>("aaa", 1) const funcA: <string | number>(...args: (string | number)[]) => (string | number)[]