代码之家  ›  专栏  ›  技术社区  ›  NSjonas

泛型类型的判别并

  •  6
  • NSjonas  · 技术社区  · 8 年前

    我想可以用 union discrimination 一个普通的。然而,它似乎不起作用:

    示例代码( view on typescript playground) :

    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 失去的是歧视。

    2 回复  |  直到 8 年前
        1
  •  10
  •   nickf    7 年前

    问题

    受歧视的联合中的类型缩小受到以下几个限制:

    不展开泛型

    首先,如果类型是泛型的,泛型将不会被展开以缩小类型范围:缩小范围需要联合才能工作。例如,这不起作用:

    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;
      }
    };
    
        2
  •  0
  •   Romain Deneau    8 年前

    很好的一点是 genericThing.item 被看作是 Foo 里面 if 块。我以为只有在把它提取成一个变量后它才能工作( const item = genericThing.item )可能是最新版本ts的更好表现。

    这样就可以像在函数中一样进行模式匹配 area 在官方文件中 Discriminated Unions 而在c(v7中,a default 案件仍然是必要的 switch 像这样的声明)。

    事实上,奇怪的是 genericThing 仍被视为未受歧视的 GenericThing<Foo | Bar> 而不是 GenericThing<Foo> ),即使在 如果 块在哪里 item 是一个 !那么错误与 fooThing.item.fooProp; 不会让我吃惊的。

    我想typescript团队还需要做一些改进来支持这种情况。

    推荐文章