ジェネリックで nion discrimination を使用できるようにしたいと思います。ただし、機能していないようです。
コード例( TypeScriptプレイグラウンドのビュー) :
interface Foo{
type: 'foo';
fooProp: string
}
interface Bar{
type: 'bar'
barProp: number
}
interface GenericThing<T> {
item: T;
}
let func = (genericThing: GenericThing<Foo | Bar>) => {
if (genericThing.item.type === 'foo') {
genericThing.item.fooProp; // this works, but type of genericThing is still GenericThing<Foo | Bar>
let fooThing = genericThing;
fooThing.item.fooProp; //error!
}
}
TypeScriptが、汎用のitem
プロパティを区別したため、genericThing
はGenericThing<Foo>
。
これはサポートされていないのでしょうか?
また、直接割り当てた後、それがfooThing.item
それは差別を失います。
差別化された共用体での型の絞り込みには、いくつかの制限があります。
ジェネリックのアンラップなし
まず、タイプがジェネリックの場合、ジェネリックはアンラップされてタイプを絞り込みません。ナローイングが機能するにはユニオンが必要です。したがって、たとえばこれは機能しません:
let func = (genericThing: GenericThing<'foo' | 'bar'>) => {
switch (genericThing.item) {
case 'foo':
genericThing; // still GenericThing<'foo' | 'bar'>
break;
case 'bar':
genericThing; // still GenericThing<'foo' | 'bar'>
break;
}
}
これはしますが:
let func = (genericThing: GenericThing<'foo'> | GenericThing<'bar'>) => {
switch (genericThing.item) {
case 'foo':
genericThing; // now GenericThing<'foo'> !
break;
case 'bar':
genericThing; // now GenericThing<'bar'> !
break;
}
}
Union型の引数を持つジェネリック型をラップ解除すると、コンパイラチームが満足のいく方法で解決できないあらゆる種類の奇妙なコーナーケースが発生するのではないかと思います。
ネストされたプロパティによる絞り込みなし
タイプの和集合がある場合でも、ネストされたプロパティでテストする場合、ナローイングは発生しません。テストに基づいてフィールドタイプを絞り込むことができますが、ルートオブジェクトは絞り込まれません。
let func = (genericThing: GenericThing<{ type: 'foo' }> | GenericThing<{ type: 'bar' }>) => {
switch (genericThing.item.type) {
case 'foo':
genericThing; // still GenericThing<{ type: 'foo' }> | GenericThing<{ type: 'bar' }>)
genericThing.item // but this is { type: 'foo' } !
break;
case 'bar':
genericThing; // still GenericThing<{ type: 'foo' }> | GenericThing<{ type: 'bar' }>)
genericThing.item // but this is { type: 'bar' } !
break;
}
}
解決策は、カスタムタイプガードを使用することです。 type
フィールドを持つ任意の型パラメーターで機能する、かなり一般的なバージョンの型ガードを作成できます。残念ながら、GenericThing
に関連付けられるため、ジェネリック型には使用できません。
function isOfType<T extends { type: any }, TValue extends string>(
genericThing: GenericThing<T>,
type: TValue
): genericThing is GenericThing<Extract<T, { type: TValue }>> {
return genericThing.item.type === type;
}
let func = (genericThing: GenericThing<Foo | Bar>) => {
if (isOfType(genericThing, "foo")) {
genericThing.item.fooProp;
let fooThing = genericThing;
fooThing.item.fooProp;
}
};
式genericThing.item
がFoo
ブロック内でif
として表示されるのは良い点です。変数(const item = genericThing.item
)に抽出した後にのみ機能すると思いました。おそらくTSの最新バージョンの動作が改善されています。
これにより、 Discriminated Unions の公式ドキュメントの関数area
のようなパターンマッチングが有効になり、C#には実際にありません(v7では、default
のケースはまだこのようなswitch
ステートメントで必要です)。
実際、奇妙なのは、genericThing
ブロック内でif
がitem
が無差別に(GenericThing<Foo | Bar>
ではなくGenericThing<Foo>
として)見られることです。 _はFoo
です!その場合、fooThing.item.fooProp;
のエラーは私を驚かせません。
TypeScriptチームには、この状況をサポートするためにまだ改善すべき点があると思います。