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

时间:2018-08-16 14:35:14

标签: typescript generics

假设我有一个如下通用接口:

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>的类型?

2 个答案:

答案 0 :(得分:3)

(在下面使用TS 3.0)

如果TypeScript直接支持existential types,我会告诉您使用它们。存在类型意味着“我所知道的就是该类型存在,但我不知道或不在乎它是什么”。然后,您的transforms参数具有类似Array< exists A. Transform<A> >的类型,表示“对于 some Transform<A>来说,A的事物数组”。有suggestion允许使用该语言的这些类型,但是很少有人支持这种语言,谁知道呢。

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

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

但是,正如您所说的,即使没有存在性类型,您也希望实现一致性。幸运的是,有一些变通办法,它们的复杂程度各不相同。这是一个:


让我们声明一个类型函数,该类型函数使用T,如果它是 some Transform<A>的{​​{1}},则返回A(新的top type,它与每个值都匹配...因此,对于所有unknownunknown & T都等于T),否则返回T({{3} },它不匹配任何值...因此,对于所有nevernever & T等于never):

T

它使用bottom type进行计算。这个想法是,它查看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

并从您想要的任何interface SomeTransform { transformerWithArg: (input: string) => string; } 中创建一个SomeTransform

Transform<A>

然后接受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


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

答案 1 :(得分:0)

您可以使用通用元组rest参数(在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]