简体   繁体   English

如果接口在 TypeScript 中,如何在数组中指定泛型

[英]How to specify generics in an array if interfaces in TypeScript

I have a function that takes an array of specifications as an argument.我有一个函数,它将一系列规范作为参数。 The shape of these specifications are defined, but the values can be anything.这些规范的形状已定义,但值可以是任何值。 However - I'd like to provide a generic to let TypeScript infer the types based on the specs I pass into the function.但是 - 我想提供一个泛型,让 TypeScript 根据我传递给函数的规范来推断类型。 Here's a very basic example:这是一个非常基本的例子:

interface Spec<YourType> {
    value: YourType;
}

function doStuff<T>(specs: Spec<T>[]): T[] {
    return specs.map(spec => spec.value);
}

const mySpecs = [
    {value: 1},
    {value: 'two'},
    {value: [1, 2, 3]},
];

const values = doStuff(mySpecs);

I understand it can't really infer types here because they're all different, but can I somehow add them as types explicitly to the doStuff generic.我知道它不能在这里真正推断类型,因为它们都是不同的,但是我可以以某种方式将它们作为类型显式添加到 doStuff 泛型中。 Something like this pseudo-code:像这样的伪代码:

function doStuff<T>(specs: <Spec<t> for t of T>): <t for t of T> {
    return specs.map(spec => spec.value);
}

const values = doStuff<[number, string, number[]]>(mySpecs);

TypeScript 3.1 introduced support for using mapped tuple types , so if you have a type like [1,2,3] and apply a mapped type like {[K in keyof T]: Spec<T[K]>} to it, it will become [Spec<1>, Spec<2>, Spec<3>] . TypeScript 3.1 引入了对使用映射元组类型的支持,所以如果你有一个像[1,2,3]这样的类型并应用像{[K in keyof T]: Spec<T[K]>}这样的映射类型,它将变成[Spec<1>, Spec<2>, Spec<3>] Therefore the signature for your function could be something like this:因此,您的函数的签名可能是这样的:

declare function doStuff<T extends readonly any[]>(
  specs: { [K in keyof T]: Spec<T[K]> } | []
): T;

That's fairly close to your pseudo-code.这与您的伪代码非常接近。 The only differences of note is that we are saying that T has to be either an Array or ReadonlyArray (a ReadonlyArray is considered a wider type than Array ), and the specs parameter's type has a union with an empty tuple type | []唯一需要注意的区别是我们说T必须是一个ArrayReadonlyArray (一个ReadonlyArray被认为是比Array更宽的类型),并且specs参数的类型有一个带有空元组类型的联合| [] | [] in there to give the compiler a hint that it should infer T to be a tuple type if possible, instead of just an order-forgetting array. | []在那里给编译器一个提示,如果可能的话,它应该推断T是一个元组类型,而不仅仅是一个忘记顺序的数组。 Here's what usages would look like:以下是用法的样子:

const values = doStuff([
  { value: 1 },
  { value: 'two' },
  { value: [1, 2, 3] },
]);
// const values: [number, string, number[]]

If you want to do that in two separate lines with mySpecs you should probably tell the compiler not to forget that mySpecs is a tuple type, like this:如果你想用mySpecs在两个单独的行中做到这一点,你应该告诉编译器不要忘记mySpecs是一个元组类型,像这样:

const mySpecs = [
  { value: 1 },
  { value: 'two' },
  { value: [1, 2, 3] },
] as const;
/* const mySpecs: readonly [
  { readonly value: 1; }, { readonly value: "two"; }, { readonly value: readonly [1, 2, 3];
}] */

const moreValues = doStuff(mySpecs);
// const moreValues: readonly [1, "two", readonly [1, 2, 3]]

Here I've used a TypeScript 3.4+ const assertion to keep the type of mySpecs narrow... maybe it's too narrow since everything becomes readonly and literal types .在这里,我使用了 TypeScript 3.4+ const断言来保持mySpecs的类型狭窄......也许它狭窄了,因为一切都变成了readonlyliteral types But that's up to you to work with.但这取决于你的工作。


This would be the end of the answer except that, unfortunately, the compiler cannot verify that the implementation of doStuff() conforms to the signature:这将是答案的结尾,但不幸的是,编译器无法验证doStuff()的实现是否符合签名:

return specs.map(spec => spec.value); // error! not callable

The easiest way around this is to use some judicious type assertions to convince the compiler first that specs has a usable map method (by saying it's a Spec<any>[] instead of just T , and that the output is indeed a T :解决这个问题的最简单方法是使用一些明智的类型断言首先说服编译器specs有一个可用的map方法(通过说它是一个Spec<any>[]而不仅仅是T ,并且输出确实是一个T

function doStuff<T extends readonly any[]>(specs: { [K in keyof T]: Spec<T[K]> } | []): T {
  return (specs as Spec<any>[]).map(spec => spec.value) as readonly any[] as T;
}

That compiles now and should be close to what you're looking for.现在编译并且应该接近您正在寻找的内容。 Okay, hope that helps;好的,希望有帮助; good luck!祝你好运!

Playground link to code Playground 链接到代码

You will have to provide union type definition of YourType to make it work:您必须提供YourType联合类型定义才能使其工作:

const mySpecs = [
    {value: 1},
    {value: 'two'},
    {value: [1, 2, 3]},
];

type YourType = number | string | number[];

interface Spec<YourType> {
    value: YourType;
}

function doStuff<T>(specs: Spec<T>[]): T[] {
    return specs.map(spec => spec.value);
}

const values = doStuff<YourType>(mySpecs);
console.log(values);

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

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