数日前にTypeScript 5.7 RCがアナウンスされてリリースが楽しみだなー!ってところなんだけど、そのさらに数日前に、ウォッチしていたこのPR↓がマージされてTypeScript 5.8.0のマイルストーンに入った。わー!
これが今日のお話。TypeScript 5.8.0でConditional return type narrowingが入りそう。楽しみ!
Conditional return type narrowing?
直訳すると「条件付き戻り値型の絞り込み」かな。引数の型によって戻り値の型が変わる関数を定義したいときに、例えばこんな風に書きたくなる。
declare const record: Record<string, string[]>; declare const array: string[]; function getObject<T extends string | undefined>( group: T ): T extends string ? string[] : T extends undefined ? Record<string, string[]> : never { if (group === undefined) { return record; } return array; } const arrayResult = getObject("group"); const recordResult = getObject(undefined);
この例では、引数である group
の型が文字列(またはそのサブタイプ)だったら文字列の配列を、undefined
だったら Record<string, string[]>
を返すように定義してある。
でも、このコードはエラーになる
2024-11-07現在のTypeScript 5.6.3では、このコードはエラーになる。呼び出す側のコードはエラーにならずに、渡した引数の型に合わせて正しく戻り値の型が判断されるんだけど(arrayResult
の型はstring[]
になっている)、関数の実装側の return
部分がエラーになる。
「if
ブロックの中に入るってことは group
が undefined
だってことだから戻り値の型は Record<string, string[]>
だし、if
ブロックの中に入らなかったら文字列ってことなんだから戻り値の型は string[]
やん!」って思うけどTypeScriptは分かってくれない。
そこは分かってくれるとうれしいなぁ。という要望に対応したのが、今回のPR。
分かってくれる。そう、5.8ならね。
やったー!↑は、5.8.0-dev.20241106を使っている(画像だけ貼られてもなぁというのはそれはそう)。
制約
この特別なチェックは、一般的な条件付き戻り値型に対して実施できるわけではなくて、制約がある。その制約とは↓この形式になっていること。
T extends A ? AType : T extends B ? BType : never
つまり
- 条件型が分配できること(
T
でチェックしていること) - 最後に
never
を持つこと infer
型パラメタを持たないことT extends [infer A] ? AType : ...
みたいなのはダメ
それに加えて
- 上記の
A
とB
がT
の構成要素になっていること
ということで全体としてはこんな形↓になる。
function fun<T extends A | B>(param: T): T extends A ? AType : T extends B ? BType : never { if (isA(param)) { ... } else { ... } }
この形なら、戻り値の条件型に対するナローイングが効く。
インデックスアクセス型
インデックスアクセス型の場合も上記の条件を満たすので、ナローイングができる。
9月に書いたこの記事で
↓これがエラーになると書いたが、5.8ではエラーにならない。やったねー。
type Mapping = { a: boolean, b: string, } function getValue<K extends "a" | "b">(key: K): Mapping[K] { if (key === "a") { return true; } return "foo"; }
おしまい
面白いなー。制約はあるけど「まさにこういうことがやりたい」ってときにシュッと思い出せたら便利だなー!