在泛型类中期望函数并建议函数名



大家好。😊

这是对这个问题的追问。

interface Works {
call(): void;
}
interface DoesntWork {
value: number;
}
interface ShouldWork {
value: number;
call(): void;
}
class Handler<T extends { [K in keyof T]: () => any }> {
public send<P extends Extract<keyof T, string>>(
methodName: P,
payload: T[P],
) {
//
}
}
const handlerA = new Handler<Works>(); // ok
const handlerB = new Handler<DoesntWork>(); // error: ok
const handlerC = new Handler<ShouldWork>(); // error: should work
handlerA.send('call', () => null); // ok
handlerB.send('value', 23); // shouldn't work
handlerC.send('call', () => null); // ok
handlerC.send('value', 42); // 'value' shouldn't work and shouldn't be suggested

Handler应接受ShouldWork,因为接口中至少存在一个功能(call)。

handlerC.send不应该建议value,因为它是一个数字而不是一个函数

这是一个操场。

我的方法是编写一个AcceptableHandler<T>实用程序类型,将T转换为可接受的版本,以便每个存在的属性都是零参数函数。

type AcceptableHandler<T> =
HasProp<PickByValue<T, () => any>, { needsAtLeastOneZeroArgMethod(): any }>

你可以这样读:AcceptableHandler<T>首先是PickByValue<T, ()=>any>,这意味着它只是T的那些属性是零参数函数(它就像Pick<T, K>实用程序类型,除了它由属性而不是属性),然后我们应用HasProp<⋯, {needsAtLeastOneZeroArgMethod(): any}>,这意味着如果结果甚至有一个属性,它就会单独留下,否则它将被{needsAtLeastOneZeroArgMethod(): any}取代,这应该在没有单个可接受的send参数的情况下产生错误消息。

我们还必须定义PickByValue<T, V>HasProp<T, D>:

type PickByValue<T, V> =
{ [K in keyof T as T[K] extends V ? K : never]: T[K] }
type HasProp<T, D = never> =
T extends (keyof T extends never ? never : unknown) ? T : D

第一个是键重映射类型,用于过滤属性键,第二个是条件类型,用于检查是否缺少已知键。

有了这些,我们可以这样写Handler<T>:
class Handler<T extends AcceptableHandler<T>> {
public send<P extends keyof AcceptableHandler<T>>(
methodName: P,
payload: T[P],
) {
//
}
}

首先,我们将T限制为AcceptableHandler<T>,这意味着除非T包含至少一个可以用作零参数函数的属性,否则我们将得到错误:

const handlerA = new Handler<Works>(); // okay
const handlerB = new Handler<DoesntWork>(); // error
// ------------------------> ~~~~~~~~~~
// Type 'DoesntWork' does not satisfy the 
// constraint '{ needsAtLeastOneZeroArgMethod(): any; }'.
const handlerC = new Handler<ShouldWork>(); // okay

然后我们将sendP类型参数约束为AcceptableHandler<T>的键,这意味着它只接受零参数方法属性的键:

handlerA.send('call', () => null); // okay
handlerB.send('value', 23); // error
// ---------> ~~~~~~~
// Argument of type '"value"' is not assignable to 
// parameter of type '"needsAtLeastOneZeroArgMethod"'.
handlerC.send('call', () => null); // okay
handlerC.send('value', 42); // error
// ---------> ~~~~~~~
// Argument of type '"value"' is not assignable to
// parameter of type '"call"'.

请注意,handlerB上的错误有点奇怪,因为它期望"value""needsAtLeastOneZeroArgMethod",如果你实际使用它,肯定会在运行时做坏事,但是handlerB本身在创建时已经是一个错误,所以之后得到的任何错误都不那么重要。如果您解决了new Handler<DoesntWork>()处的原始错误,那么send()处的错误应该消失或被更有用的错误所取代。

Playground链接到代码

相关内容

  • 没有找到相关文章

最新更新