この投稿では、TypeScriptの型から再帰的にオプショナルを外すユーティリティ型の実装方法を紹介します。
結論
- 先に結論としてオプショナルを外すユーティリティ型の実装を示しておきます:
type RecursiveRequired<T> = {
[P in keyof T]-?: RecursiveRequired<T[P]>
}
- これと同等のことをするライブラリもあります: piotrwitek/utility-types
「再帰的にオプショナルを外す型」ってどういう型?
TypeScriptにはビルトインのユーティリティ型にRequired<T>
というものがあります。
例えば、次のFoo
のようにオプショナルなプロパティを持つ型に対して、
type Foo = {
a?: number
b?: string
}
Required
ユーティリティ型を使うと、オプショナルなプロパティをすべてオプショナルじゃなくできます:
type RequiredFoo = Required<Foo>
//=> { a: number, b: number } 型になる
Required
はある型から別の型を計算できるので、コードの重複が減らせて便利ですが、オプショナルを外せるのはトップレベルのプロパティだけという制約があります。どういうことかというと、次の例のようにオブジェクトがネストした構造の型の子オブジェクトや孫オブジェクトのプロパティのオプショナルは外せないのです:
type Oya = {
ko?: {
mago?: {
a?: string
}
}
}
type RequiredOya = Required<Oya>
このRequiredOya
はko
プロパティだけオプショナルが外れますが、ko.mago
やko.mago.a
はオプショナルなままです。型の計算結果は次のようになります:
type RequiredOya = {
ko: {
mago?: {
a?: string
}
}
}
この投稿で紹介する「再帰的にオプショナルを外す型」とは、以上の課題を解決するもので、ネストした構造の型でも子オブジェクトや孫オブジェクトをたどっていって、すべてのプロパティからオプショナルを外す型になります。
RecursiveRequired<T>
- 再帰的にオプショナルを外すユーティリティ型
再帰的にオプショナルを外すユーティリティ型は以下のように定義されます:
type RecursiveRequired<T> = {
[P in keyof T]-?: RecursiveRequired<T[P]>
}
RecursiveRequired<T>
のデモ
RecursiveRequired<T>
を試してみます。
まず、再帰的な構造の型を定義します。もちろんプロパティはすべてオプショナルにします:
type Node = {
name?: string
parent?: Node
children?: Array<Node>
}
本当にオプショナルになっているかコンパイルエラーを起こして確かめましょう:
// 上の型を値として定義
declare const node: Node
// stringしか受け取らない関数を定義
declare function test(value: string): void
// テスト
test(node.name)
test(node.parent.name)
test(node.parent.parent.name)
test(node.parent.parent.parent.name)
test(node.children[0].name)
test(node.children[0].children[0].name)
test(node.children[0].children[0].children[0].name)
コンパイル結果は期待通りすべてエラーになります:
今度はオプショナルだらけのNode
をRecursiveRequired<T>
に渡して、オプショナルが完璧に外れた型に対してテストしてみます。再帰的にオプショナルが外れていれば、上のテストコードはコンパイルが通るはずです。
declare const node: RecursiveRequired<Node>
コンパイル結果は、期待通りエラーが起きませんでした: