【问题标题】:Generic class transformation in TypeScriptTypeScript 中的泛型类转换
【发布时间】:2022-01-26 03:14:46
【问题描述】:

我正在做一个项目,我需要实例化一个特定类的多个副本,但参数不同。我正在创建一个实用函数 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 的理解。另外,我用工厂方法打了similar issues,所以看起来构造方法无论如何都是要走的路。

【问题讨论】:

  • 有趣的问题。让withAlteredConstructorArgs 为您调用构造函数的动机是什么? const cycler = new Cycler(reverseElementArgs(['foo', 'bar', 'baz', 'quux'])); 会有同样的效果吗?
  • 我很想直接调用它,但这是一个服务管理器,它将遍历列表并为列表中的每个配置创建一个新的服务实例。

标签: typescript generics


【解决方案1】:

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

我以为我根据你编写的代码理解了你的问题直到我读了那个,这让我不太确定你想成为哪个论点创建约束的类型信息的提供者以及您希望约束的参数。

我将根据您编写代码的方式做出回应:您希望作为 first 参数提供的构造函数的参数为​​Args 和回调函数来获取该类型的参数。

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

这里另一个潜在的混淆点是您似乎想要更高种类的类型,但that's not available in TypeScript(至少)。似乎您希望 createCycler 是通用的(它不在您的示例中),并且您可以通过对其使用通用类型断言来使其通用。 (在其他情况下,您可以使用泛型类型 annotation,但我认为这在这里行不通。)

TS Playground

// 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"

【讨论】:

  • 你爆发Ctor的方式正是我所缺少的——谢谢!我的 Cycler 示例是一个糟糕的示例,因为它引入了额外的复杂性;感谢您创建 Person 示例,因为它是一个更好的插图。
【解决方案2】:

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

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

TS Playground

【讨论】:

  • 目前Cycler.elements 可以是异构元组,但您建议进行更改,将其限制为同构数组。
  • 如果E 是联合类型,E[] 也不能是异构的吗?我想我在这里的理解存在差距。
  • 同构,即每个项目必须属于同一类型(当然也可以是联合)
  • 我认为这说明了为什么我的建议不支持与原来相同的情况:tsplay.dev/W4yROw
  • 我认为这是一个既简单又清晰的演示!
猜你喜欢
  • 2019-05-30
  • 2018-09-21
  • 2012-11-28
  • 1970-01-01
  • 1970-01-01
  • 2017-12-28
  • 2011-08-15
  • 2022-08-27
  • 1970-01-01
相关资源
最近更新 更多