🎳
TypeScript: 複数のオブジェクト型から、名前も型も共通するプロパティを抜き出す型演算
問題
typescriptのオブジェクト型が複数あるとして、名前も型も同じプロパティをどう抜き出せばいいか?
まずは2つの場合から考えてみましょう。具体的には下記のDogCat型をうまく型演算で表現したいということです。
type Dog = {
name: string
height: number
weight: number
kind: string // kindがstring型
}
type Cat = {
name: string
height: number
kind: number // kindがnumber型
}
type DogCat = ?????
// DogとCatとで名前も型も同じプロパティだけ抜き出したい
// type DogCat = {
// name: string
// height: number
// }
答え
2通りありえますが、1つ目の方がエレガントですね👶
// 1 (mapped typeを使う)
// Pickの第2項では、名前も型も同じプロパティが自身のプロパティ名を型として、
// mapped typeを構成して(このとき、名前だけ同じで型が違うプロパティはnever型)、
// そこから名前が共通するプロパティ群をunion型で与えてプロパティを抜き出している
// (第2項のmapped typeは
// { name: "name", height: "height", kind: never}[ "name" | "height" | "kind"]
// となり、結果的に "name" | "height" となる)
type PickSameProperties<T1, T2> = Pick<T1, {
[K in keyof T1 & keyof T2]:
T1[K] extends T2[K] ?
T1[K] extends T2[K] ? K : never
: never;
}[keyof T1 & keyof T2]>;
type DogCat = PickSameProperties<Dog, Cat>
/////////////////////////
// 2 (union distributionを使う)
// SamePropertiesではunion distributionを使って、
// 名前も型も同じプロパティをunion型として抜き出しており、
// PickSamePropertiesでは2つの型を与えると、
// SamePropertiesで作ったunion型で一方の型からプロパティをPickする
type SameProperties<T1, T2, K extends keyof T1 & keyof T2> = NonNullable<
K extends
(T1[K] extends T2[K]
? T2[K] extends T1[K]
? keyof T1
: never
: never)
? K
: null>
type PickSameProperties<T1, T2> = Pick<T1, SameProperties<T1, T2, keyof T1 & keyof T2>>
type DogCat = PickSameProperties<Dog, Cat>
2つと言わずに複数の型に一気に適用したい場合は、下記の記事中にある型演算を組み合わせれば良さそうです👶
なお、1つ目の方法は下記で紹介されています👀
使いみちの例(めちゃ個別具体的)
tiptapというwysiwygライブラリを使っているとき、ドキュメント内の要素を読み込みモードではNode型として、編集モードではMark型として扱わなければならないことがありました。
ライブラリの初期化段階ではユーザーがどちらのモードで使うか確定しないため、要素についてNode型にもMark型にも共通するプロパティだけを先に処理する必要があり、その際に今回の型演算を使いました。
(めちゃくちゃ具体的すぎてやばいですね)
Discussion