使用单个必需属性和无限制附加属性声明类型

时间:2019-04-29 14:53:05

标签: reactjs typescript

我想声明一个函数,该函数带有一个参数,该参数的类型定义为具有单个必需属性和任意数量的其他属性打开格式(T)的对象,同时要求其他属性遵守T的类型签名。具体来说,我正在尝试执行以下操作:

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

以上内容绝对无效。我已经尝试过使用[x: string]: any;的方法,但这太宽容了,并且允许偏离T的类型签名。

1 个答案:

答案 0 :(得分:0)

TypeScript当前对所讨论的类型没有很好的支持。问题在于,如果存在索引签名,则与该索引签名相对应的所有命名属性都必须与其兼容。假设无法将string分配给T,则类型{key: string, [k: string]: T}将不起作用。这种限制是有道理的,但有时却令人沮丧。

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

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

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,然后使用intersection类型确认P符合您的要求。它非常长且混乱,实际上需要我将其设为conditional函数,以允许您手动指定T,但随后让编译器推断P(另一个当前{{3 }}(在TypeScript中):

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 } }] });

我可以看到这对您来说不太方便,但是值得您避免让编译器与您抗争的麻烦。

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