假设我有一个如下所示的通用接口(interface):
interface Transform<ArgType> {
transformer: (input: string, arg: ArgType) => string;
arg: ArgType;
}
然后我想应用这些 Transform
的数组到 string
.如何定义这个 Transform
数组这样它就可以验证 <ArgType>
在 Transform.transformer
中是等价的和 Transform.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 直接支持 existential types ,我会告诉你使用它们。存在类型意味着“我只知道该类型存在,但我不知道也不关心它是什么”。那么你的transforms
参数的类型类似于 Array< exists A. Transform<A> >
,意思是“对于一些 Transform<A>
来说是A
的数组”。有一个suggestion在语言中允许这些类型,但很少有语言支持这一点,所以谁知道呢。
你可以“放弃”,只使用 Array<Transform<any>>
,这会起作用,但无法捕获像这样的不一致情况:
applyTransforms("hey", [{transformer: repeat, arg: "oops"}]); // no error
但正如您所说,即使在没有存在类型的情况下,您也在寻求强制一致性。幸运的是,有不同复杂程度的变通方法。这是一个:
让我们声明一个接受 T
的类型函数, 如果它是 Transform<A>
对于一些 A
, 它返回 unknown
(新的 top type 匹配每个值...所以 unknown & T
等于 T
对于所有 T
),否则返回 never
(bottom type 不匹配任何值...所以 never & T
等于 never
对于所有 T
):
type VerifyTransform<T> = unknown extends
(T extends { transformer: (input: string, arg: infer A) => string } ?
T extends { arg: A } ? never : unknown : unknown
) ? never : unknown
它使用 conditional types计算那个。这个想法是它看 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
你得到一个更合理的错误:“Types of parameters 'arg' and 'arg' are incompatible. Type 'string' is not assignable to type 'number'.
”
好的,希望对你有帮助。祝你好运。
关于typescript - 如何在 TypeScript 中定义泛型数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51879601/