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",
}