🫧
【TypeScript】TupleとArrayとそれ以外のケースで、よしなに型関数を適用する型
OCP(開放閉鎖の原則)に則った拡張性の高さを意識したコードを書いている時に出てきがちな、
- ある型がtupleの場合、 たとえば
[number, number]
と型関数F<T>
から[F<number>, F<number>]
を作りたい - ある型が配列の場合、 たとえば
number[]
と型関数F<T>
からF<number>[]
を作りたい - それ以外の場合、たとえば
{ id: number }
と型関数F<T>
からF<{ id: number }>
を作りたい
を実現する型の書き方は次のようになる
type IsTuple<T> = T extends readonly any[]
? number extends T['length']
? false
: true
: false
type ApplySomeFunc<I> =
IsTuple<I> extends true
? {[K in keyof I]: F<I[K]>}
: I extends Array<infer U>
? Array<F<U>>
: F<I>
TypeScriptでは型関数の引数に型関数を渡すことはできない(渡したい…)ので、ApplySomeFunc
に相当する型関数はF<T>
の数だけ書くことになる。isTuple
はutils/type.ts
的なところに入れておけばいい。
I
がtupleの時(たとえば、[number, number]
とする)に{[K in keyof I]: SomeFunc<I[K]>}
と書くことで、[F<number>, F<number>]
が得られる挙動は、Mapped Tuple Typeと呼ばれる、Mapped Type とは少し異なるイレギュラーな挙動である。
type SomeTuple = [number, number]
type A = Tuple['map'] // これは型エラーにはならない
このように作成したSomeTuple型は、Array.prototypeが持っているメソッドを型情報として持つ。そのため、Mapped Typeのルールに従うと{[K in keyof I]: SomeFunc<I[K]>}
はat
などのArray
由来のプロパティを持つはずだが、実際にはそうはならない。ルールの一貫性より使いやすさが優先された結果、この挙動が追加された。(そのときのリリースノート)
参考にさせていただいた記事:
Discussion