问题描述
比方说,我有一个通用的界面,如下所示:
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.transformer
和Transform.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 A
的Transform<A>
,则返回unknown
(新的顶部类型,它与每个值都匹配...因此对于所有T
均等于T
),否则返回never
(底部类型不匹配任何值...因此,对于所有T
,never & 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中定义泛型数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!