Edited at

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

こういう2つの型を扱うことがあると思います

type Article = {id: string, value: number}

type ArticleDraft = {id: string | null, value: number}

ORM などで一度保存するまでidが振られない、みたいな時によくある型ですね。

これは簡単な例ですが、フィールドが多くなると同じような型を2つ書くのが面倒くさいし、何よりバグを仕込みそうなので、今回はなんとかして Article 型から ArtcileDraft 型を最小限の手数で生成したい、と思います。


案1: Draft を先に定義して、 extends して絞り込む

interface ArticleDraft {id: null | string, value: number}

interface Article extends ArticleDraft { id: string }

意図したとおりの2つの型が出来たのですが、発想の順序が逆です。先に Article を宣言してから ArtcileDraft を生成したいので、別の手段をとります。


案2: Conditional Type の型魔法で nullable に変換

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

// Example
type Foo = { id: string }
type FooDraft = Draft<Foo, 'id'>
// => {id: null | string}

Twitter であれこれしてたら @wonderful_panda 氏に教えてもらいました。

型のプロパティをイテレートして別の型を再構築しながら、第二型引数で渡した key を満たす型は nullable な型に変形しています。

これが便利なのは、 文字列の uniontype で複数の型を nullable にできること

type Article = {id: string, value: number}

type ArticleDraft = Draft<Article, 'id' | 'value'>
// => {id: string | null, value: number | null}

意味はわかるのですが、自分でこれをひねり出すことは出来ませんでした。とはいえイディオムの組み合わせなので、一度知ったら色々応用できそうなテクニックですね。


参考

https://qiita.com/nullabletypo/items/999fe07d079298c35e0c#omit

https://twitter.com/wonderful_panda/status/1097883246740660231