泛型类型持有带有该方法的参数的类的方法

时间:2020-08-26 07:34:58

标签: typescript typescript-typings typescript-generics

我想创建一个通用类型,该通用类型将保存带有此方法参数的类中存在的方法,但是当我向通用提供类实例时,会得到[从不,从不]。

我会在类中使用这种类型,因此我提供了一个潜在用法的简化示例

在类中的用法示例

type AnyClass = { new (...arg0: any): any }

type SingeTask<V extends InstanceType<AnyClass>> = [
  Extract<keyof V, (...arg0: any) => any>,
  Parameters<Extract<keyof V, (...arg0: any) => any>>
]

type Task<V extends InstanceType<AnyClass>> = {
  task: SingeTask<V>
  resolve: (value?: unknown) => void
  reject: (reason: any) => void
}

class Queue<T extends AnyClass> {
  instances: InstanceType<T>[] = []
  queue: Task<T>[] = []
  object: T

  constructor(object: T) {
    this.object = object
  }

  addInstance( number:number) {
      for (i; i < number; i++) {
        this.instances.push(new this.object())
  }

  

  runTasksInQueue(
    instance: InstanceType<T>,
    { task, reject, resolve }: Task<T>
  ) {
    try {
      const respone = Reflect.apply(instance, ...task)
      resolve(respone)
    } catch (error) {
      reject(error)
    } finally {
      const task = this.queue.pop()
      if (task) {
        this.runTasksInQueue(instance, task)
      } else {
        this.instances.push(instance)
      }
    }
  }

  addTaskToQueue(task: SingeTask<T>) {
    return new Promise((resolve, reject) => {
      const instance = this.instances.pop()
      if (instance) {
        this.runTasksInQueue(instance, { task, resolve, reject })
      } else {
        this.queue.push({ task, resolve, reject })
      }
    })
  }
}

队列类的用法

class mockClass {
  sth?: number
  constructor(n?: number) {
    this.sth = n
  }
  getNumber(n: number) {
    return n
  }
}

const instanceQueue = new queue(mockClass)
instanceQueue.addInstance(5)
instanceQueue.addTaskToQueue() // Here I get error [never,never] but I would expect to be able to pass [getNumber,(number e.g - 10)] and receive 10

1 个答案:

答案 0 :(得分:1)

虽然您的代码在意图上基本上是正确的,但首先要解决两个问题:

  1. V extends InstanceType<AnyClass>并不意味着V将成为传入的实例类型,而只是意味着V必须扩展{{1}的任何实例类型。 }是AnyClass。因此,这减少为any。因此,当您在代码中获得V extends any时,SingeTask<typeof mockClass>将是V而不是typeof mockClass的实例类型。您需要删除约束,并在需要实例类型的任何地方使用mockClass

  2. InstanceType<V>不会仅将Extract<keyof V, (...arg0: any) => any>的键筛选为具有特定值的键。 V将从联合中取出,某些类型扩展了第二个参数。因此,例如Extract将是Extract<"a" | "b", "a">。或更有用的是,"a"将是圆圈(Playground Link)。要按特定类型获取密钥,您需要类似this答案中看到的Extract<Circle | Rectangle, { type: "circle" }>类型(我将仅使用KeyOfType,您可以阅读参考答案中的表达式,以了解如何有效)

将这两个观察结果放在一起,我们得到KeyOfType的第一个可用迭代:

SingleTask

Playground Link

首先进行测试看起来不错:

type KeyOfType<T, U> = {[P in keyof T]-?: T[P] extends U ? P: never}[keyof T]
type ValuesOfType<T, U> = {[P in keyof T]-?: T[P] extends U ? T[P]: never}[keyof T]
// Alternate version of ValuesOfType
// type ValuesOfType<T, U> = Extract<T[keyof T], U>

type SingeTask<V extends AnyClass> = [
  KeyOfType<InstanceType<V>, (...arg0: any) => any>,
  Parameters<ValuesOfType<InstanceType<V>, (...arg0: any) => any>>
]

但是随后我们发现了一个微妙的问题,这是有效的:

class mockClass {
  public field: string = ""
  getNumber(n: number) { return n }
  getString(s: string) { return s }
}

const instanceQueue = new Queue(mockClass)
instanceQueue.addInstance(5)
instanceQueue.addTaskToQueue(["getNumber", [1]]) // one number is ok ✅
instanceQueue.addTaskToQueue(["getString", ["A"]]) // one string is ok ✅
instanceQueue.addTaskToQueue(["getString", ["A", "B"]]) // not ok ✅
instanceQueue.addTaskToQueue(["field", []]) // no fields ✅

问题在于元组的第一个成员与第二个成员之间没有关系。应用于模拟类instanceQueue.addTaskToQueue(["getString", [1]]) // Allowed ? 的结果为类型SingeTask。尽管这样做确实提供了一定的安全性,但这确实意味着我们可以将["getNumber" | "getString", [number] | [string]]的参数与getNumber的名称混合使用。

对此的解决方案是生成一个元组的联合,而不是如上所述的具有联合成员的元组。基本上,我们需要getString

我们可以应用与["getNumber", [number]] | ["getString", [string]]相同的思想,并得到包含键名和参数而不是键名的元组的联合:

KeyOfType

Playground Link

在这个新版本中,我们无法将一个函数的参数与另一个函数的名称混合

type SingeTask<V extends AnyClass> = {
  [P in keyof InstanceType<V>]-?: InstanceType<V>[P] extends (...a: any) => any ? [P, Parameters<InstanceType<V>[P]>]: never
}[keyof InstanceType<V>]