如何在TypeScript中定义泛型数组? [英] How do you define an array of generics in TypeScript?

查看:2664
本文介绍了如何在TypeScript中定义泛型数组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

比方说,我有一个通用的界面,如下所示:

Let's say I have a generic interface like the following:

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

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

And then I want to apply an array of these Transform to a string. How do I define this array of Transform such that it validates that <ArgType> is equivalent in both Transform.transformer and Transform.arg? I'd like to write something like this:

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>接口?

In this example, what type do I define const transforms as in order for the type system to validate that each item in the array satisfies the generic Transform<ArgType> interface?

推荐答案

(在下面使用TS 3.0)

(Using TS 3.0 in the following)

如果TypeScript直接支持现有类型,我会告诉您使用它们.存在性类型意味着诸如我所知道的就是该类型存在,但是我不知道或不在乎它是什么"之类的东西.然后,您的transforms参数具有类似Array< exists A. Transform<A> >的类型,表示对于 some A来说,是Transform<A>的事物的数组".有一个建议允许使用该语言的这些类型,但是很少有语言支持此功能.知道.

If TypeScript directly supported existential types, I'd tell you to use them. An existential type means something like "all I know is that the type exists, but I don't know or care what it is." Then your transforms parameter have a type like Array< exists A. Transform<A> >, meaning "an array of things that are Transform<A> for some A". There is a suggestion to allow these types in the language, but few languages support this so who knows.

您可以放弃"并只使用Array<Transform<any>>,该方法可以工作,但无法捕获如下不一致的情况:

You could "give up" and just use Array<Transform<any>>, which will work but fail to catch inconsistent cases like this:

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

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

But as you said you're looking to enforce consistency, even in the absence of existential types. Luckily, there are workarounds, with varying levels of complexity. Here's one:

让我们声明一个使用T的类型函数,如果它是 some ATransform<A>,则返回unknown(新的底部类型不匹配任何值...因此,对于所有Tnever & T均等于never):

Let's declare a type function which takes a T, and if it a Transform<A> for some A, it returns unknown (the new top type which matches every value... so unknown & T is equal to T for all T), otherwise it returns never (the bottom type which matches no value... so never & T is equal to never for all T):

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兼容.

It uses conditional types to calculate that. The idea is that it looks at transformer to figure out A, and then makes sure that arg is compatible with that A.

现在我们可以将applyTransforms作为通用函数输入,它仅接受transforms参数,该参数与T类型的元素匹配VerifyTransform<T>的数组匹配:

Now we can type applyTransforms as a generic function which only accepts a transforms parameter which matches an array whose elements of type T match 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;
}

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

Here we see it working:

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

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

If you pass in something inconsistent, you get an error:

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'.",但至少是一个错误.

The error isn't particularly illuminating: "[ts] Argument of type '{ transformer: (input: string, arg: number) => string; arg: string; }[]' is not assignable to parameter of type 'never'." but at least it's an error.

或者,您可能会意识到,如果您正在做的只是将arg传递给transformer,则可以像这样创建类似于存在的SomeTransform类型:

Or, you could realize that if all you're doing is passing arg to transformer, you can make your existential-like SomeTransform type like this:

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

,然后从您想要的任何Transform<A>中创建一个SomeTransform:

and make a SomeTransform from any Transform<A> you want:

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

如果您尝试执行不一致的操作:

And if you try to do it inconsistently:

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

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

you get an error which is more reasonable: "Types of parameters 'arg' and 'arg' are incompatible. Type 'string' is not assignable to type 'number'."

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

Okay, hope that helps. Good luck.

这篇关于如何在TypeScript中定义泛型数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆