Union Typeのプロパティに対してOmitするとうまくいかない問題について

下記のようなUnion Typeから`name` propertyだけ取り除いた
`OmittedType`を作りたいとします。


``` typescript
type Type = {
    name: string,
    type: 1,
    prop1: string,
} | {
    name: string,
    type: 2,
    prop2: string,
}

/** 下記のような型を作りたい
type OmittedType =  {
    type: 1,
    prop1: string,
} | {
    type: 2,
    prop2: string,
}
**/
```

この時素直にOmitすると
``` typescript
type OmittedType = Omit<Type, "name">  // =  {type: 1 | 2;}
```

となってしまい`prop1` `prop2`のプロパティが消えてしまい
うまくいきません。
なぜこうなるのでしょうか？

Omitの実装は、下記のようになっています。
```typescript
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
```
渡された型Tのキーを除いたものをPickするという、シンプルな実装に見えます。

そこで順番に確認してみます。
``` typescript
type KeyType = keyof Type
```

`KeyType`は`"name" | "type" | "prop1" | "prop2"`になって欲しいですが、実際は`"name" | "type"`になります。これはTypeScriptの仕様として、keyofの対象がUnionだった場合、その中の共通して存在するキーだけが取り出されるためです。(https://github.com/microsoft/TypeScript/issues/12948)

そのため、`Omit<Type, "name"> `の型は内部的には`Pick<Type, Exclude<"name"|"type", "name">>`となり
`Type`から`type`プロパティだけを取り出すことになるので `{type: 1 | 2;}`となってしまうわけです。


# 解決策

Conditional Typesを用いて次のような型を作成すれば、うまくUnion Typeのプロパティに対してOmitすることができます。


``` typescript
type TheOmit<T, K extends keyof T> = T extends T ? Omit<T, K> : never

type NewOmittedType = TheOmit<Type, "name">
/**
型補完は次のようになる
type NewOmittedType = Omit<{
    name: string;
    type: 1;
    prop1: string;
}, "name"> | Omit<{
    name: string;
    type: 2;
    prop2: string;
}, "name">
**/
```


これはUnion Distributionをうまく使ったものです。

Union DistributionはUnionが型変数であるとき起こる特殊な挙動です。
例えば、下記のような実装をしたとき`BoxedValue`はどんな型になっているでしょうか？

``` typescript
type BoxedValue<T> = { value: T };
type DistributedBoxedValue = BoxedValue<string | number>;
```

普通に考えると、`{ value: string | number }`になりそうなのですが
```typescript
{ value: string } | { value: number }
```
という型になります。

これがUnion Distributionの挙動で、Unionの分配が起きると、
`BoxedValue<string> | BoxedValue<number>`のように
型変数が分配されてから型の計算が行われます。

ここでもう一度`TheOmit`の実装を見ています。
``` typescript
type TheOmit<T, K extends keyof T> = T extends T ? Omit<T, K> : never
```


下記で内部に起こっていることを順を追って説明します。

```typescript
type Type = {
    name: string,
    type: 1,
    prop1: string,
} | {
    name: string,
    type: 2,
    prop2: string,
}
type TheOmit<Type, "name">
```



`Type`の型を展開
```typescript
type TheOmit<{
    name: string,
    type: 1,
    prop1: string,
} | {
    name: string,
    type: 2,
    prop2: string,
}, "name">
```
Union Distributionにより型が分配される
```typescript
TheOmit<{
    name: string,
    type: 1,
    prop1: string,
} , "name"> | TheOmit<{
    name: string,
    type: 2,
    prop2: string,
} , "name"> 

```
`TheOmit`の中身に当てはめる(片側のみ)
```typescript
{
    name: string,
    type: 1,
    prop1: string,
} extends {
    name: string,
    type: 1,
    prop1: string,
} ? Omit<{
    name: string,
    type: 1,
    prop1: string,
}, "name"> : never
```
もちろんextendsはtrueなので
```typescript
Omit<{
    name: string,
    type: 1,
    prop1: string,
}, "name"> 
```
もう片方も同様に
```typescript
Omit<{
    name: string;
    type: 2;
    prop2: string;
}, "name">
```
よって`TheOmit<Type, "name">`は
```typescript
 Omit<{
    name: string;
    type: 1;
    prop1: string;
}, "name"> | Omit<{
    name: string;
    type: 2;
    prop2: string;
}, "name">
```

という型になります。





## 参考

https://github.com/microsoft/TypeScript/issues/54525

https://zenn.dev/t_keshi/articles/tips-typescript-2