やりたいこと
const numbers = [1, 2, 4, 5] as const
const foo = ['f', 'o', 'o'] as const
let numbers_len1: Length<typeof numbers> = 4; //OK
let numbers_len2: Length<typeof numbers> = 5; //エラー
let foo_len1: Length<typeof numbers> = 3; //OK
let foo_len2: Length<typeof numbers> = 4; //エラー
となるように,配列の長さを宣言するLength typeを作りたい。
結論
- 解決法1
type Length<T extends ReadonlyArray<any>> = T['length']
- 解決法2
type Length<T extends readonly any[]> = T['length']
- 解決法3
type Length<T extends any> = T extends { length : infer R } ? R : never;
解説
すべてGenericsを使っています。
ジェネリクスに関しては色んな方が分かりやすい記事をあげているのでそちらを参考にしてください。
https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a#%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%82%AF%E3%82%B9
解決法1
type Length<T extends ReadonlyArray<any>> = T['length']
ReadonlyArrayという型がTypeScriptに用意されています。
The ReadonlyArray type describes Arrays that can only be read from
function foo(arr: ReadonlyArray<string>) {
arr.slice(); // okay
arr.push("hello!"); // error!
}
名前の通り,配列を変更するようなメソッドは使えませんが,今回のようにlengthを取得することはできます。
解決法2
type Length<T extends readonly any[]> = T['length']
解決法1とほぼ同じです。
今回は配列をas constでreadonlyとしているので,
type Length<T extends any[]> = T['length']
とすると以下のエラーが発生してしまうので注意してください。
The type 'readonly [1, 2, 4, 5]' is 'readonly' and cannot be assigned to the mutable type 'any[]'.
解決法3
type Length<T extends any> = T extends { length : infer R } ? R : never;
inferについてはこちらの記事が分かりやすいです。
T.lengthが存在する場合はそのlengthを返し,存在しない場合はneverを返しています。
補足
TypeChallengeというサイトに問題がたくさん載っていて,その中のLength of Tupleという問題になっています。
上記以外にもたくさん解答があるので参考にしてください。
https://github.com/type-challenges/type-challenges/issues?q=label%3A18+label%3Aanswer