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] は 分配なしでチェックする と見なすべきです。
追伸:もっと面白い例は私のブログで確認できます
