【问题标题】:Generic type holding a method of class with parameters of this method持有类方法的泛型类型,带有该方法的参数
【发布时间】:2020-08-26 16:05:18
【问题描述】:

我想创建一个泛型类型,该类型将保存一个存在于具有该方法参数的 clas 上的方法,但是当我将类实例提供给泛型时,我得到 [never, never]。

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

类中的示例用法

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

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

    虽然您的代码在意图上大部分是正确的,但首先有两个问题:

    1. V extends InstanceType&lt;AnyClass&gt; 并不意味着V 将是传入的任何实例类型,它只是意味着V 必须扩展AnyClass 的实例类型,即any。所以这减少到V extends any。因此,当您在代码中获得SingeTask&lt;typeof mockClass&gt; 时,V 将是typeof mockClass 而不是mockClass 的实例类型。您需要删除约束并在需要实例类型的任何地方使用InstanceType&lt;V&gt;

    2. Extract&lt;keyof V, (...arg0: any) =&gt; any&gt; 不会将V 的键过滤为仅具有特定值的键。 Extract 会从联合中取出一些扩展第二个参数的类型。例如Extract&lt;"a" | "b", "a"&gt; 将是"a"。或者更有用的是 Extract&lt;Circle | Rectangle, { type: "circle" }&gt; 将是圆形 (Playground Link)。要获取特定类型的密钥,您需要类似于您在this 答案中看到的KeyOfType 类型(我将只使用KeyOfType,您可以阅读参考答案中关于其工作原理的解释)

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

    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>>
    ]
    

    Playground Link

    一开始测试看起来不错:

    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 相同的想法,并获得包含键名和参数的元组的并集,而不仅仅是键名:

    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>]
    

    Playground Link

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

    instanceQueue.addTaskToQueue(["getString", [1]]) // Error ✅
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多