ウェブサイト検索

TypeScriptでユニオン型と交差型を組み合わせる際のパラメータ型の解決


ここに3つの簡単な種類があります

type T1 =
  | { letter: 'a'; valueFunc: (prop: number) => void; valueType: number }
  | { letter: 'b'; valueFunc: (prop: string) => void; valueType: string }

type T2 = { base: 'low' }

type T3 = T1 & T2

そして2つの簡単な定義

const var1: T3 = { letter: 'b', base: 'low', valueFunc: (prop) => {}, valueType: 'empty' }

const var2: T3 = { letter: 'a', base: 'low', valueFunc: (prop) => {}, valueType: 0 }

これは期待通りに完璧に動作します。TSはvalueFunc内のpropの型を正しく評価します。しかし、T2に別の型のユニオンを追加すると、TSはもはやpropを解決できなくなりますが、valueTypeは依然として解決できます。

修正済みタイプ

type T1 =
  | { letter: 'a'; valueFunc: (prop: number) => void; valueType: number }
  | { letter: 'b'; valueFunc: (prop: string) => void; valueType: string }

type T2 = { base: 'low' } | {noise: 'high'}

type T3 = T1 & T2

const var1: T3 = { letter: 'b', base: 'low', valueFunc: (prop) => {}, valueType: 'empty' }

const var2: T3 = { letter: 'a', noise: 'high', valueFunc: (prop) => {}, valueType: 0 }

それはなぜですか?私には何が欠けているのでしょうか?

回答 #1

TypeScriptは識別付きユニオンとうまく機能します

T1 は差別化されています。なぜなら各ユニオンが letter プロパティを持っているからです。一方で T2 はそうではありません。この問題を解決する方法は二つあります。

最初の方法 ただ discriminator を T2 に追加します。例えば:

type T2 = { type: '1', base: 'low' } | { type: '2', noise: 'high' }

2つ目の方法 あなたの T2 のユニオンをより厳密にしてください。詳細な説明はこの回答を参照してください:

type T1 =
  | { letter: 'a'; valueFunc: (prop: number) => void; }
  | { letter: 'b'; valueFunc: (prop: string) => void; }


type UnionKeys<T> = T extends T ? keyof T : never;

type StrictUnionHelper<T, TAll> =
  T extends any
  ? T & Partial<Record<Exclude<UnionKeys<TAll>, keyof T>, never>> : never;

type StrictUnion<T> = StrictUnionHelper<T, T>

type T2 = StrictUnion<{ base: 'low' } | { noise: 'high' }>

type T3 = T1 & T2

const var1: T3 = { letter: 'b', base: 'low', valueFunc: (prop) => { } } // prop is string

const var2: T3 = { letter: 'a', noise: 'high', valueFunc: (prop) => { } } // prop is number

遊び場

経験則:もし各オブジェクトが異なり共通点がないユニオンがある場合は、識別子を追加してください。

さらに詳しい説明

1)

なぜ私たちは keyof T の代わりに UnionKeys を使うのか

私たちは T extends any も使用できます。ここでの主なポイントは、分配性のために条件付き型を使用することです。なぜでしょう?なぜなら、keyof ({a:1}|{b:2}) を使用すると、共通のプロパティがないため never が返されるからです。こちらをご覧ください。

それは、あなたが使用しているときに意味します:

type UnionKeys<T> = T extends any ? keyof T : never;

keyof T は、ここで T extends any を使用しているため、ユニオン全体ではなく、ユニオン内の各要素に個別に適用されます。

一般的に、T extends any は 分配を有効にする と見なし、[T] extends [any] は 分配なしでチェックする と見なすべきです。

追伸:もっと面白い例は私のブログで確認できます