代码之家  ›  专栏  ›  技术社区  ›  Alberto Rivera

Typescript—使用泛型扩展具有联合类型属性的接口

  •  0
  • Alberto Rivera  · 技术社区  · 8 年前

    我有一个这样的接口

    interface Test {
      values: string[] | number[] | Date[]
      // more properties
    }
    

    我想创建另一个使类型特定的接口。我尝试的是:

    interface TestWithType<T extends string | number | Date> extends Test {
      values: T[];
    }
    

    但这失败了,错误是 Type 'string' is not assignable to type 'Date' .

    正确的语法是什么?If属性 values 不是数组,这可以正常工作

    interface Test {
      values: string | number | Date
    }
    
    interface TestWithType<T extends string | number | Date> extends Test {
      values: T;
    }
    
    2 回复  |  直到 8 年前
        1
  •  2
  •   jcalz    8 年前

    正如你所注意到的,你不明白 string[] | number[] | Date[] 通过创建类型为 string | number | Date (string | number | Date)[] 宽度大于 ,则会出现编译器错误,因为无法扩展子类型的属性。

    如果你有这样的工会 字符串|编号|日期 字符串[]|数字[]|日期[] ,您可以使用 distributive conditional types :

    type DistributeArray<T> = T extends any ? T[] : never;
    

    然后你可以定义 TestWithType DistributeArray :

    // no error:
    interface TestWithType<T extends string | number | Date> extends Test {
      values: DistributeArray<T>;
    }
    

    并验证其行为是否符合预期:

    declare const testWithString: TestWithType<string>
    testWithString.values; // string[]
    
    declare const testWithDate: TestWithType<Date>
    testWithDate.values; // Date[]
    
    declare const testWithStringOrNumber: TestWithType<string | number>
    testWithStringOrNumber.values; // string[] | number[]
    

    希望有帮助。祝你好运!


    编辑:

    作为一个相关的问题,有没有办法禁止将联合类型传递给泛型?(如要求只指定字符串、数字或日期中的一个)

    可能的 ,但它需要以一种让我不舒服的方式滥用类型系统。如果你不需要的话,我建议你不要那样做。在这里:

    type DistributeArray<T> = T extends any ? T[] : never;
    type NotAUnion<T> = [T] extends [infer U] ? U extends any ? 
      T extends U ? unknown : never : never : never
    type ErrorMsg = "NO UNIONS ALLOWED, PAL"
    interface TestWithType<T extends (
      unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg
    )> extends Test {
      values: DistributeArray<T>
    }
    
    declare const testWithString: TestWithType<string> // okay
    
    declare const testWithDate: TestWithType<Date> // okay
    
    declare const testWithStringOrNumber: TestWithType<string | number> // error:
    // 'string | number' does not satisfy the constraint '"NO UNIONS ALLOWED, PAL"'.
    

    NotAUnion<T> 计算结果为 unknown T 不是工会。。。否则它的计算结果是 never (底部类型)。然后,在 测试类型 类型 T型 unknown extends NotAUnion<T> ? string | number | Date : ErrorMsg ,勉强避开了循环引用的规则。如果你通过了一个非工会组织 T型 ,它变成 T extends string | number | Date 和以前一样。如果你通过工会,它就变成 T extends ErrorMsg ErrorMsg ,所以开发人员应该有足够的希望了解正在发生的事情。它是有效的,但它是非常粗略的东西。不过,这是你自找的。

        2
  •  1
  •   Matt McCutchen    8 年前

    两种可能的解决方案,使用查找类型或条件类型:

    enum TestType { STRING, NUMBER, DATE }
    interface TestTypeMapping {
      [TestType.STRING]: string[];
      [TestType.NUMBER]: number[];
      [TestType.DATE]: Date[];
    }
    interface TestWithType1<T extends TestType> extends Test {
      values: TestTypeMapping[T];
    }
    
    type TypeToArrayType<T> =
      T extends string ? string[] :
      T extends number ? number[] :
      T extends Date ? Date[] :
      never[];
    interface TestWithType2<T extends string | number | Date> extends Test {
      values: TypeToArrayType<T>;
    }
    
    推荐文章