【问题标题】:Declare type with single required property and unbounded additional properties声明具有单个必需属性和无限附加属性的类型
【发布时间】:2019-04-30 00:28:29
【问题描述】:

我想声明一个函数,该函数接受一个参数,该参数的类型定义为具有单个必需属性和任意数量的附加属性开放格式 (T) 的对象,同时要求附加属性遵守类型签名的T。具体来说,我正在尝试做这样的事情:

export myFunc<T>(props: { 
  data: {
    key: string;
    [x: T]: any;
  }[]
}) { // myFunc code... }

以上绝对行不通。我已经尝试过使用[x: string]: any; 的方法,但这过于宽松,并且允许偏离T 的类型签名。

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    TypeScript 目前对您所说的类型没有很好的支持。问题是,如果存在索引签名,则与索引签名对应的所有命名属性都必须与之兼容。假设string 不能分配给T,则{key: string, [k: string]: T} 类型将不起作用。这种限制是有道理的,但有时它会让人感到沮丧。

    也许在可预见的将来,arbitrary index signature typesnegated types 将成为可能;如果是这样,您可能会将类型表示为{key: string; [K: string &amp; not "key"]: T}。也就是说,您将能够从索引签名中明确排除 "key"。但我们还没有。

    您可以做的一件事是使用intersection 之类的{key: string} &amp; {[k: string]: T} 来规避问题。但这在某些情况下不起作用,尤其是您的情况,您可能会传入对象文字:

    declare function myFunc<T>(props: {
      data: Array<{
        key: string
      } & { [x: string]: T }>
    }): void;
    
    myFunc<number>({ data: [{ key: "a" }] }); // error!
    //                      ~~~~~~~~~~~~ <-- key is incompatible with index signature
    

    另一种解决方法是使myFunc 成为泛型函数,将props 参数的类型推断为泛型类型P,然后使用conditional 类型来验证P 是否满足您的要求.它很长而且很乱,实际上需要我将其设为curried 函数,以允许您手动指定T,然后让编译器推断P(TypeScript 中另一个当前missing feature 的结果):

    type EnsureProps<
      P extends { data: Array<{ key: string }> },
      T,
      A extends any[] = P['data']
      > = {
        data: {
          [I in keyof A]: {
            [K in keyof A[I]]?: K extends 'key' ? string : T
          }
        }
      };
    
    declare const myFuncMaker: <T>() =>
      <P extends {
        data: Array<{ key: string }>
      }>(props: P & EnsureProps<P, T>) =>
        void;
    

    但至少它确实有效:

    const myFunc = myFuncMaker<number>();
    
    myFunc({ data: [{ key: "a", dog: 1, cat: "2" }] }); // error!
    //                                  ~~~ <-- string is not assignable to never
    
    myFunc({ data: [{ key: "a", dog: 1 }, { key: "b", cat: 4 }] }); // okay
    

    所以让我们退后一步。所有这些要么是不可行的,有问题的,要么是类型判断的噩梦。编译器真的不想让你代表这种类型。我可能会建议您考虑重构 myFunc() 函数,以便参数更适合 TypeScript 的类型系统。例如,如果您将 T 类型的附加属性下推一级,但将 "key" 保留在原处,则效果会很好:

    declare function myFunc<T>(props: {
      data: Array<{
        key: string,
        more?: { [x: string]: T };
      }>
    }): void;
    
    myFunc<number>({ data: [{ key: "a", more: { a: 1, b: 2 } }] });
    

    我知道这对你来说有点不方便,但它可能是值得的,因为它可以让你免于让编译器与你对抗的头痛。

    无论如何,希望对您有所帮助;祝你好运!

    【讨论】:

    • 这很好地说明了问题——感谢您将其分解得如此彻底!
    猜你喜欢
    • 2022-01-17
    • 2018-09-06
    • 1970-01-01
    • 2019-10-14
    • 2018-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多