大家好。😊
这是对这个问题的追问。
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
然后我们将send
的P
类型参数约束为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链接到代码