やりたかったこと
タイトルの通りですが、もともとのモチベーションは「あるジェネリック型の要素を持つ配列」を、「別のジェネリック型の要素を持つ配列」に変換(このとき型引数は引き継ぐ)したいというものでした。色々試行錯誤した結果、対象を配列ではなくタプル型で定義し、Variadic Tuple Typesを使うと目的の型定義ができることがわかりました。
// イメージ
const beforeList = [Before<number>, Before<string>, Before<number>]
const afterList = convert(beforeList)
// afterListの型は [After<number>, After<string>, After<number>] になる
上の通り、Before<T>
型の要素を持つ配列があり、これを convert
という関数に渡すと、各要素の型引数 T
を引き継いだまま、After<T>
の配列が帰ってくるというイメージです。この convert
に型付けしたいというのがモチベーションです。
※ 前述の通り、最終的にはタプル型で定義しています。
実装
いきなり結論ですが、以下で定義できました。
// サンプルとして Before<T> 型と After<T> 型を定義
type Before<T> = {
type: 'before',
value: T,
}
type After<T> = {
type: 'after',
value: T,
}
// convert の型定義 (関数本体の実装は省略しています。)
function convert<T extends ReadonlyArray<Before<unknown>>>(beforeList: [...T]): [
...{ [K in keyof T]: T[K] extends Before<infer U> ? After<U> : never }
]
// テスト
const before1: Before<number> = {
type: 'before',
value: 1,
}
const before2: Before<string> = {
type: 'before',
value: 'string',
}
const afterList = convert([before1, before2]); // [After<number>, After<string>] 型になっている
上では convert
の引数の中で [before1, before2]
と書いているので、これがタプル型と判断されています。ポイントは Variadic Tuple Types によって要素数が不定のタプルの中身を ...T
で受けられていることです。受けた後は T
の各要素にインデックスアクセスすることができ、例のように Map してそれぞれの要素から型変数を取り出すことができています。
もし上の例で before のタプルを関数の外で定義する場合は、
// before のリストを外で定義する場合
const beforeList: [Before<number>, Before<string>] = [before1, before2];
const afterList = convert(beforeList);
のように、明示的にタプル型で定義することが必要です。
昨年いくつかVariadic Tuple Typesの紹介記事を目にしたときには、自分が TypeScript に不慣れだったこともありいつ使うものなんだろうと思っていましたが、今回必要に迫られて利用してみるととても便利な型であることがわかりました。