【TypeScript】nullableなプロパティをすべてnon-nullableにするユーティリティ型
はじめに
こんにちは kimizuy です。
本記事では undefined
や null
の型注釈のあるプロパティを持つオブジェクト型から、それらの nullable な型注釈を取り除き、どの階層のプロパティもすべて non-nullable な型に変換するカスタムユーティリティ型を紹介します。
例えば、SSG のようにビルド時にページ生成する場合など、生成に必要なデータがなければランタイムエラーになる状況があります。この場合、「型は nullable だけど実態としては必須のデータ」は開発では存在チェックやオプショナルチェーンしないで non-nullable に扱いたいですよね。そういったときに、このカスタムユーティリティ型の利用が考えられます。
実際には、以下の Foo
型のように複数階層まで nullable まみれの型から undefined
と null
を取り除きます。
type Foo =
| {
a1?: string;
b1: string | null;
c1?: { c2: string | undefined };
}
| undefined;
↓
type Foo = {
a1: string;
b1: string;
c1: { c2: string };
};
tldr
TypeScript がデフォルトで持つ NonNullable
ユーティリティ型を利用し、再帰型を作ります。-?
は見慣れないですが、このように書くことでオプショナル修飾子 ?
を取り除けます。
type Primitive = number | string | boolean | bigint | symbol | undefined | null;
type Builtin = Primitive | Function | Date | Error | RegExp;
type DeepNonNullable<T> = T extends Builtin
? NonNullable<T>
: { [key in keyof T]-?: DeepNonNullable<T[key]> };
追記(2022/10/25): 再帰の方法が間違っていてループになっていたようなので修正しました。以下のリンクの記事が参考になりました。
TypeScriptの再帰テクニック
以下は使用例です。 すべてのプロパティが必須になっている foo
変数は c2
プロパティが定義されていないため型エラーになっています。
type Foo =
| {
a1?: string;
b1: string | null;
c1?: { c2: string | undefined };
}
| undefined;
type AllNonNullableFoo = DeepNonNullable<Foo>;
const foo: AllNonNullableFoo = { a1: "A", b1: "B", c1: {} };
// エラー
// Property 'c2' is missing in type '{}' but required in type '{ c2: string; }'.
この例は TypeScript Playground でも試せます。
補足: なんで Required<Type>
じゃなくて NonNullable<Type>
を使うの?
TypeScript には他にも NonNullable<Type>
に似た Required<Type>
というデフォルトのユーティリティ型があります。この型は明示的な null
や undefined
を取り除かず、オプショナル修飾子 ?
だけを取り除きます。
type Required<T> = { [P in keyof T]-?: T[P]; }
(自分が混同していたので備忘録として残します)
参考
おわりに
Gatsby では GraphQL の型を生成してくれる神機能がありますが、生成された型には基本的に null
の型注釈がついてきます。それを解決したくてこの型を作ってみました。プロパティがすべて必須になるためリスクを意識しつつ使っていきたいですね。
以上、お読みいただきありがとうございました。
弊社ではJamstackの知見で事業作りに貢献したいフロントエンドエンジニアを募集しています。大きな制作会社や事業会社とはひと味もふた味も違うGaji-Laboを味わいに来ませんか?
もちろん、一緒にお仕事をしてくださるパートナーさんも随時募集中です。まずはお気軽に声をかけてください。お仕事お問い合わせや採用への応募、共に大歓迎です!
求人応募してみる!