⚠️

TypeScriptのOmit<T,Keys>を使うと、TとKeysに共通していないプロパティが消える

2024/12/20に公開2

背景

例えば以下のような型を定義する。
この時 ABCx ではA B Cのすべてからyを取り除いた値を取得したかったがそうはならなかった。

A B Cすべてに共通する { x: number; y: number } から y を削除したものが残った。

type A = { a: string; x: number; y: number }
type B = { b: string; x: number; y: number }
type C = { c: string; x: number; y: number }
type ABC = A | B | C

// 期待したもの:   { a: string; x: number } | { b: string; x: number } | { c: string; x: number }
// 作成されたもの: { x: number }
type ABCx = Omit<ABC, 'y'>

TypeScript Playground

原因

結論: OmitExcludeを使っているから。

詳しく

Omitの定義は以下のようになっている。

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

今回、TABCK extends keyof Ty

Exclude<keyof T, K>keyof ABC では、 ABC で共通するプロパティである "x" | "y" が返される。
この時点でa b cは消えるのでPickもされない。

回避方法

頑張ってOmit(ボツ)

以下のように各型ごとにOmitしたものをユニオン型にするという書き方にした。
(最初これ書いたけどユニオン分配の書き方がイケてるのでボツ)

type OmittedA = Omit<A, 'y'>
type OmittedB = Omit<B, 'y'>
type OmittedC = Omit<C, 'y'>
type OmittedABCx = OmittedA | OmittedB | OmittedC // = Omit<A, "y"> | Omit<B, "y"> | Omit<C, "y">

const testA: OmittedABCx = { a: '', x: 0 }
const testB: OmittedABCx = { b: '', x: 0 }
const testC: OmittedABCx = { c: '', x: 0 }

ユニオン分配(Union Distribution)を使う

@adanamiさんにコメントで教えていただきました🙇🏻‍♂️
こっちのほうがイケてる👍

type OmitY<T> = T extends { y: unknown } ? Omit<T, 'y'> : never
type NewABCx = OmitY<ABC> // = Omit<A, "y"> | Omit<B, "y"> | Omit<C, "y">

Discussion

adanamiadanami

コメント失礼します
ExtractExcludeでも使われているUnion distributionという機能を使うことでも回避できるのでご参考までに

type OmitY<T> = T extends { y: unknown } ? Omit<T, 'y'> : never
type ABCx = OmitY<ABC> // = Omit<A, "y"> | Omit<B, "y"> | Omit<C, "y">
saneatsusaneatsu

ありがとうございます!

なるほど〜〜〜。記事更新させていただきました🙇🏻‍♂️