繁体   English   中英

如何在TypeScript中定义泛型数组?

[英]How do you define an array of generics in TypeScript?

假设我有一个如下通用界面:

interface Transform<ArgType> {
    transformer: (input: string, arg: ArgType) => string;
    arg: ArgType;
}

然后我想将这些Transform的数组应用于string 如何定义此Transform数组,以便验证<ArgType>Transform.transformerTransform.arg是否相同? 我想写这样的东西:

function append(input: string, arg: string): string {
    return input.concat(arg);
}

function repeat(input: string, arg: number): string {
    return input.repeat(arg);
}

const transforms = [
    {
        transformer: append,
        arg: " END"
    },
    {
        transformer: repeat,
        arg: 4
    },
];

function applyTransforms(input: string, transforms: \*what type goes here?*\): string {
    for (const transform of transforms) {
        input = transform.transformer(input, transform.arg);
    }

    return input;
}

在这个例子中,我定义了const transforms的类型,以便类型系统验证数组中的每个项是否满足泛型Transform<ArgType>接口?

(以下使用TS 3.0)

如果TypeScript直接支持存在类型 ,我会告诉你使用它们。 存在主义类型意味着“我所知道的是类型存在,但我不知道或不关心它是什么。” 然后你的transforms参数有一个像Array< exists A. Transform<A> >这样的类型,意思是“对于某些 A来说是一个Transform<A>的数组”。 有一个建议允许这些类型的语言,但很少有语言支持这个谁知道。

您可以“放弃”并使用Array<Transform<any>> ,这将起作用但无法捕获这样的不一致情况:

applyTransforms("hey", [{transformer: repeat, arg: "oops"}]); // no error

但正如你所说,即使在没有存在类型的情况下,你也希望强化一致性。 幸运的是,有一些变通方法具有不同程度的变通方法。 这是一个:


让我们来声明一个类函数,它接受一个T的,如果它Transform<A>一些 A ,它返回unknown (新的顶级型 ,其每一个值相匹配......所以unknown & T等于T所有T )否则它会返回never (不匹配任何值的底部类型 ......所以never & T等于所有T never ):

type VerifyTransform<T> = unknown extends
  (T extends { transformer: (input: string, arg: infer A) => string } ?
    T extends { arg: A } ? never : unknown : unknown
  ) ? never : unknown

它使用条件类型来计算它。 这个想法是它看着transformer找出A ,然后确保arg与那个A兼容。

现在我们可以输入applyTransforms作为泛型函数,它只接受一个transforms参数,该参数匹配类型为T的元素匹配VerifyTransform<T>的数组:

function applyTransforms<T extends Transform<any>>(
  input: string, 
  transforms: Array<T> & VerifyTransform<T>
): string {
  for (const transform of transforms) {
    input = transform.transformer(input, transform.arg);
  }
  return input;
}

在这里,我们看到它工作:

applyTransforms("hey", transforms); // okay

如果传入不一致的内容,则会出现错误:

applyTransforms("hey", [{transformer: repeat, arg: "oops"}]); // error

错误并不是特别有启发性:“ [ts] Argument of type '{ transformer: (input: string, arg: number) => string; arg: string; }[]' is not assignable to parameter of type 'never'. “但至少这是一个错误。


或者,你可以意识到,如果你所做的只是将arg传递给transformer ,你可以像这样SomeTransform类似于存在主义的SomeTransform类型:

interface SomeTransform {
  transformerWithArg: (input: string) => string;
}

并根据您想要的任何Transform<A>制作SomeTransform

const makeSome = <A>(transform: Transform<A>): SomeTransform => ({
  transformerWithArg: (input: string) => transform.transformer(input, transform.arg)
});

然后接受一个SomeTransform数组:

function applySomeTransforms(input: string, transforms: SomeTransform[]): string {
  for (const someTransform of transforms) {
    input = someTransform.transformerWithArg(input);
  }

  return input;
}

看看它是否有效:

const someTransforms = [
  makeSome({
    transformer: append,
    arg: " END"
  }),
  makeSome({
    transformer: repeat,
    arg: 4
  }),
];

applySomeTransforms("h", someTransforms);

如果你尝试不一致地做:

makeSome({transformer: repeat, arg: "oops"}); // error

你得到一个更合理的错误:“ Types of parameters 'arg' and 'arg' are incompatible. Type 'string' is not assignable to type 'number'.


好的,希望有所帮助。 祝好运。

您可以使用通用元组休息参数(在TS 3.0中添加)来执行此操作。

type TransformRest<T extends any[]> = {
   [P in keyof T]: T[P] extends T[number] ? Transform<T[P]> : never
}

function applyTransforms<T extends any[]>(input: string, ...transforms: TransformRest<T>): string {
   for (const transform of transforms) {
      input = transform.transformer(input, transform.arg);
   }

   return input;
}

// Makes a tuple from it's arguments, otherwise typescript always types as array
function tuplify<TS extends any[]>(...args: TS) {
   return args;
}

// Use like this:
const transforms = tuplify(
   {
      transformer: append,
      arg: " END"
   },
   {
      transformer: repeat,
      arg: 4
   },
);

//And call apply transforms like this:
applyTransforms("string", ...transforms)

//or like this:
applyTransforms("string", transform1, transform2)

说明

Typescript具有非常强大的类型推断,但通常选择最宽松的类型。 在这种情况下,您需要强制它将您的变换视为元组,以便每个元素都有自己的类型,然后让推理完成剩下的工作。

我用映射类型做了这个,对此的一个打嗝是Typescript将使用所有元组键(例如“length”),而不仅仅是数字键。 你只需要强制它只映射数字。 因此条件: T[P] extends T[number]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM