차별화된 제네릭 유형의 유형
저는 제네릭과 조합 차별을 사용할 수 있으면 좋겠습니다.하지만 효과가 없는 것 같습니다.
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!
}
}
나는 내가 일반적인 것을 구별했기 때문에 타자기가 그것을 인식하기를 바라고 있었습니다.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;
}
}
유니온 형식 인수가 있는 제네릭 형식을 풀면 컴파일러 팀이 만족스러운 방식으로 해결할 수 없는 모든 종류의 이상한 코너 사례가 발생할 것으로 예상됩니다.
중첩 특성에 의한 축소 없음
유형의 조합이 있더라도 중첩된 특성에서 테스트를 수행해도 범위가 좁혀지지 않습니다.테스트에 따라 필드 유형을 좁힐 수 있지만 루트 개체는 좁히지 않습니다.
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;
}
};
@Titian이 설명했듯이, 문제는 여러분이 정말로 필요로 하는 것이 다음과 같을 때 발생합니다.
GenericThing<'foo'> | GenericThing<'bar'>
하지만 당신은 다음과 같이 정의된 것이 있습니다.
GenericThing<'foo' | 'bar'>
이와 같은 두 가지 선택사항만 있다면 스스로 확장할 수 있지만, 물론 확장할 수는 없습니다.
노드가 있는 재귀 트리가 있다고 가정해 보겠습니다.이것은 단순화입니다.
// different types of nodes
export type NodeType = 'section' | 'page' | 'text' | 'image' | ....;
// node with children
export type OutlineNode<T extends NodeType> = AllowedOutlineNodeTypes> =
{
type: T,
data: NodeDataType[T], // definition not shown
children: OutlineNode<NodeType>[]
}
표시되는 OutlineNode<...>
차별적인 연합이 되어야 하는데, 그들은 그것 때문입니다.type: T
소유물.
노드의 인스턴스가 있고 아이들을 통해 반복된다고 가정해 보겠습니다.
const node: OutlineNode<'page'> = ....;
node.children.forEach(child =>
{
// check a property that is unique for each possible child type
if (child.type == 'section')
{
// we want child.data to be NodeDataType['section']
// it isn't!
}
})
이 경우 가능한 모든 노드 유형을 가진 하위 항목을 정의하고 싶지 않습니다.
다른 안은폭 '하발는 '니것 '' '▁theode '입니다.NodeType
우리가 아이들을 정의하는 곳.안타깝게도 형식 이름을 추출할 수 없기 때문에 제네릭으로 만들 방법을 찾을 수 없었습니다.
대신 다음을 수행할 수 있습니다.
// create type to take 'section' | 'image' and return OutlineNode<'section'> | OutlineNode<'image'>
type ConvertToUnion<T> = T[keyof T];
type OutlineNodeTypeUnion<T extends NodeType> = ConvertToUnion<{ [key in T]: OutlineNode<key> }>;
그렇다면 정의는children
변경 내용:
children: OutlineNodeTypeUnion<NodeType>[]
이제 아이들을 통해 반복하면 모든 가능성에 대한 확장된 정의와 차별화된 노조 유형의 경호가 저절로 시작됩니다.
타이프가드를 사용하는 게 어때요?실제로 그럴 필요는 없습니다. 2) 각진 템플릿과 같은 것을 사용하는 경우 타이프 가드에 엄청난 호출이 필요하지 않습니다.이 방법은 실제로 자동 유형 범위를 좁힐 수 있습니다.*ngIf
하지만 불행히도 그렇지 않습니다.ngSwitch
그 표현은 좋은 지적입니다.genericThing.item
로 간주됩니다.Foo
내부에if
블록. 변수로 추출해야 작동한다고 생각했습니다(const item = genericThing.item
) 아마도 최신 버전의 TS의 더 나은 동작일 것입니다.
이렇게 하면 기능과 같은 패턴 매칭이 가능합니다.area
차별 조합에 대한 공식 문서에서 그리고 그것은 C#에서 실제로 누락되었습니다 (v7에서, a.default
케이스는 여전히 필요합니다.switch
이와 같은 진술).
정말로, 이상한 것은genericThing
여전히 차별받지 않는 것으로 보입니다.GenericThing<Foo | Bar>
대신에GenericThing<Foo>
), 심지어 내부에서도if
어디를 막다item
이다.Foo
그러면 오류가 발생합니다.fooThing.item.fooProp;
놀랍지도 않습니다.
이 상황을 지원하기 위해 TypeScript 팀이 아직 개선해야 할 부분이 있다고 생각합니다.
언급URL : https://stackoverflow.com/questions/50870423/discriminated-union-of-generic-type
'source' 카테고리의 다른 글
패키지 데이터를 설정 도구/디스트리뷰트와 함께 포함하는 방법은 무엇입니까? (0) | 2023.07.19 |
---|---|
파이프가 부러졌습니다.DistributionNotFound 오류를 해결하는 방법은 무엇입니까? (0) | 2023.07.19 |
스프링 부트에서 JSON에서 예외를 다시 던지는 방법 (0) | 2023.07.09 |
Oracle 날짜는 어떻게 구현됩니까? (0) | 2023.07.09 |
MariaDB 증분 백업으로 변경되지 않은 데이터베이스에 대한 대용량 파일 생성 (0) | 2023.07.09 |