技術とかの雑なToday I Learnedメモ

TypeScriptでオブジェクトのプロパティの一部をNonNullableにする

TypeScriptでオブジェクトのプロパティの一部をNonNullableにする

オブジェクトのプロパティをNullableからNonNullableにしたいな〜と思ってTypeScriptのUtility Typesでできないか見たんだけどできなさそうだった。

RequiredPartialはプロパティそのものをrequiredにするかoptionalにするかなので、プロパティはrequiredでいいんだけど型をT | nullからTにしたいという感じ。

またNonNullable<T>は、Tからnullとundefinedを取り除いた型を定義してくれるが、プロパティではなくTそのものになるので今回は使えない。

どうしようかと思ったらmizchiさんがリプライをくれた。

TypeScript で既にある型から一部を nullable にする型を作る - Qiita

type Nullable<T, D extends keyof T> = {
  [K in keyof T]: (K extends D ? T[K] | null : T[K])
}

type Obj = { a: string; b: number }
type NullableObj = Nullable<Obj, 'a' | 'b'> // => { a: string | null; b: number | null }

これを自分で考え出せるようになりたいが……。

まずジェネリクスの1つめの引数Tはnullableにしたいプロパティを持つオブジェクト、2つめのD extends keyof Tは、Tのプロパティ名のstring型(↑の例だと'a' | 'b')の継承型(extends)になっている。

実際の型は、まずプロパティがK in keyof Tなので、Tのプロパティ('a' | 'b')が全て入る。

値は、Conditional TypesでK extends Dに当てはまるかによって条件分岐する。

Kはプロパティで、Dはジェネリクスの2つめの引数で受け取った値。DD extends keyof Tなので、Tのプロパティのうちどれか(もしくは全て)になる。

全てのプロパティK (K in keyof T)のうち、ジェネリクスで渡されたプロパティD (D extends keyof T)だけが条件分岐のK extends Dでtrueになるので、それらだけがT[K] | nullになる。

T[K]はそのままプロパティが持つ値の型になるので、stringならstring | nullになるしnumberならnumber | nullになる。

結果として、ジェネリクスの2つめの引数にnullableにしたいプロパティを渡せばそのプロパティはnullableになり、渡さなければならないということになる。

すげえこれ、どうやって思いつくんだ……。

自分はnon nullableにしたかったので、上記の逆をやればよい。

type NonNullable<T, D extends keyof T> = {
  [K in keyof T]: (K extends D ? NonNullable<T[K]> : T[K])
}

type Obj = { a: stirng | null; b: number | null }
type NonNullableObj = NonNullable<Obj, 'a' | 'b'> // => { a: string; b: number }

ここでNonNullable<T>が生きてくる。