source

차별화된 제네릭 유형의 유형

manysource 2023. 7. 19. 21:25

차별화된 제네릭 유형의 유형

저는 제네릭과 조합 차별을 사용할 수 있으면 좋겠습니다.하지만 효과가 없는 것 같습니다.

코드 예제(유형 스크립트 놀이터의 보기):

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