interface Data1 {
foo: number
bar: string
baz: string
}
このData1というinterfaceに対して
const value: Data1 = {
foo: 1,
bar: "bar"
}
このように変数valueを定義すると当然コンパイルエラーになります。
このエラーを解消する方法として、各プロパティをnullableにしてしまう方法があります。
TSの標準ライブラリにはPartialがあり、これを使うとData1を
interface Data2 {
foo?: number
bar?: string
baz?: string
}
として扱えるようになります。
ただ、全てのプロパティを問答無用でnullableにするよりは、以下のように一部だけnullableとして扱えるようにすべきケースの方が多いかと思います。例えば以下のように扱いたい場合です。
interface Data3 {
foo?: number
bar?: string
baz: string
}
ここで、Data1の構造を維持したままData3のように扱えるようにするためのオブジェクト操作系PartiallyPartialを実装する方法について書きます。
方法
TypeScript標準ライブラリのPartialを利用して以下のように書くと実現できます。
type PartiallyPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
この後の構文解説では以下の使用例を用いていきます。
interface Data {
foo: number
bar: string
baz: string
}
/*
* T1は { foo?: number; bar?: string; baz: string } 型になる
*/
type T1 = PartiallyPartial<Data, 'foo' | 'bar'>
const value1: T1 = {
baz: "baz"
}
console.log(value1)
構文解説
PartiallyPartialの定義を分解して見ていきます。
PartiallyPartial<T, K extends keyof T>
keyof Tはtype Tのプロパティ名の直和型を表しています。
K extends keyof Tについてですが、このように記述することでKはTの部分型であるという制約をつけることができます。
使用例においてこの構文の実体はPartiallyPartial<Data, 'foo' | 'bar'>であり、keyof Tは'foo' | 'bar'となります。
Omit<T, K>
型Tの中からKに当てはまるプロパティのみを抽出した型を返します。
使用例においてこの構文の実体はPick<Data, 'foo' | 'bar'>であり、'foo' | 'bar' | 'baz'から'foo' | 'bar'を抜き取った{baz: boolean}を返してきます。
Partial<Pick<T, K>>
Partialは、冒頭でも述べたように全てのプロパティをnullableにする操作系です。
ここではPick<Data, 'foo' | 'bar'>の'foo' | 'bar'をnullableにしています。つまりData型のプロパティの中で唯一'baz'だけがnullableでなくなります。最終的に返ってくる値は{foo?: number; bar?: string}になります。
Omit<T, K> & Partial<Pick<T, K>>
これはつまり{baz: boolean}と{foo?: number; bar?: string}の積になるので、
{ foo?: number; bar?: string; baz: string }という型になります。
応用
Omitの逆であるPickは、型Tの中からKに当てはまるプロパティを除外した型を返すので、Tで指定したプロパティ以外を省略可能にすることが可能です。
type PartiallyPartial<T, K extends keyof T> = Pick<T, K> & Partial<Pick<T, K>>;
interface Data {
foo: number
bar: string
baz: string
}
/*
* T2は { foo: number; bar: string; baz?: string } 型になる
*/
type T2 = PartiallyPartial<Data, 'foo' | 'bar'>
const value2: T2 = {
foo: 1,
bar: "bar",
}