我想创建一个通用类型,该通用类型将保存带有此方法参数的类中存在的方法,但是当我向通用提供类实例时,会得到[从不,从不]。
我会在类中使用这种类型,因此我提供了一个潜在用法的简化示例
在类中的用法示例
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
答案 0 :(得分:1)
虽然您的代码在意图上基本上是正确的,但首先要解决两个问题:
V extends InstanceType<AnyClass>
并不意味着V
将成为传入的实例类型,而只是意味着V
必须扩展{{1}的任何实例类型。 }是AnyClass
。因此,这减少为any
。因此,当您在代码中获得V extends any
时,SingeTask<typeof mockClass>
将是V
而不是typeof mockClass
的实例类型。您需要删除约束,并在需要实例类型的任何地方使用mockClass
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
首先进行测试看起来不错:
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
在这个新版本中,我们无法将一个函数的参数与另一个函数的名称混合
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>]