TypeScript中的泛型类转换



我正在进行一个项目,需要实例化一个特定类的多个副本,但使用不同的参数。我正在创建一个实用函数withAlteredConstructorArgs来帮助我实现这一点,但我很难将类型弄对。

以下是我想如何使用此实用程序的示例:

const createCycler = withAlteredConstructorArgs(
Cycler,
function reverseElementArgs<T>(elements: Array<T>): [Array<T>] {
return [elements.reverse()];
}
);
const cycler = createCycler(['foo', 'bar', 'baz', 'quux']);
console.log(cycler.get(), cycler.get());
// > should log 'quux', 'baz'

我编写的函数按预期工作,但没有按预期的方式进行打字检查。我已经创建了一个TypeScript Playground来充分演示,但这里是实现:

function withAlteredConstructorArgs<Args extends Array<any>, T>(
c: { new(...args: Args): T },
fn: (...args: Args) => Args
): (...args: Args) => T {
return (...args: Args) => new c(...fn(...args))
}

我意识到我对T的约束还不够,这就是为什么我会收到错误消息:'any[]' is assignable to the constraint of type 'Elements', but 'Elements' could be instantiated with a different subtype of constraint 'any[]'我理解这个错误,并在之前修复过它,但在与类构造函数参数有关的问题中没有正确约束T的构造函数参数的正确语法是什么

我知道我可能可以通过创建一个工厂函数并从问题语句中完全删除构造函数来解决这个问题,但我觉得理解这种方法会更好地提高我对TypeScript的理解。此外,我在工厂方法中遇到了类似的问题,所以看起来构造函数方法无论如何都是可行的。

正确约束T的构造函数参数的语法是什么?

我以为我是根据你写的代码理解你的问题的,直到我读到,这让我不太确定你想让哪个参数提供创建约束的类型信息,以及你想约束哪个参数。

我将根据您编写代码的方式做出回应:您希望构造函数的参数作为第一个参数提供,以提供Args的类型信息,并希望回调函数接受该类型的参数。

一个潜在的混淆点可能是多维数组:Args是一个参数数组,Cycler的类构造函数接受一个数组作为其第一个也是唯一的参数(因此,是数组中的数组(。

另一个潜在的混淆点是,您似乎想要更高级的类型,但这在TypeScript中不可用(至少目前(。你似乎希望createCycler是泛型的(在你的例子中没有(,你可以通过对它使用泛型类型断言来使它成为泛型的。(在其他情况下,你可以使用泛型类型注释,但我认为这在这里不起作用。(

TS游乐场

// Define the function:
type Fn<
Params extends unknown[] = any[],
Result = any,
> = (...params: Params) => Result;
type Ctor<
Params extends unknown[] = any[],
Result = any,
> = new (...params: Params) => Result;
// > = { new (...params: Params): Result }; // Alt syntax
function withAlteredConstructorArgs<C extends Ctor>(
ctor: C,
fn: Fn<ConstructorParameters<C>, ConstructorParameters<C>>,
): Fn<ConstructorParameters<C>, InstanceType<C>> {
return (...args: ConstructorParameters<C>) => new ctor(...fn(...args));
}
// Use:
// First, an example using a simpler class which DOESN'T take a generic array
// and then use it as the type for its first constructor parameter:
class Person {
constructor (readonly first: string, readonly last: string) {}
log () {
console.log(this.first, this.last);
}
}
const createPerson = withAlteredConstructorArgs(
Person,
(
first, // correctly inferred as string
last, // again, string
) => [
first.toLowerCase(),
last.toUpperCase()
] as [string, string], // TS doesn't infer tuples, so an assertion is needed here
);
const p = createPerson('Wolfgang', 'Mozart');
p.log() // "wolfgang" "MOZART"
// Now, your Cycler class:
class Cycler<Elements extends Array<any>> {
private index = 0;
constructor(private elements: Elements) {}
get() {
const el = this.elements[this.index];
++this.index;
this.index %= this.elements.length
return el
}
}
// I inferred that you want this to be generic:
// so that the types of the arguments passed to the function that it RETURNS
// will be used for the resulting Cycler instance
const createCycler = withAlteredConstructorArgs(
Cycler,
(elements) => [elements.reverse()] as [unknown[]],
) as <T extends any[]>(elements: T) => Cycler<T>;
const cycler = createCycler(['foo', 'bar', 'baz', 'quux']); // Cycler<string[]>
console.log(cycler.get(), cycler.get()); // "quux" "baz"

通过以下更改,我能够编译您的代码并生成预期的输出:

  • Cycler的定义从class Cycler<Elements extends Array<any>>更改为class Cycler<E>
  • Cycler的构造函数的签名从constructor(private elements: Elements)更改为constructor(private elements: E[])

TS游乐场

最新更新