Reputation: 46
I am trying to extract the keys of an interface (using keyof
) that match an event handler pattern - that is (CustomEvent) => void
.
I have a method that seems to work and does extract only keys that match (CustomEvent) => void
, however, it also extracts keys who's type is () => void
.
Is there a way to extract only keys of types that conform to (CustomEvent) => void
?
The method is based on the questions Filter interface keys for a sub-list of keys based on value type and Typescript : Filter on keyof type parameter
In the example below
export interface JayCustomEvent {}
type EventHandler = (e: JayCustomEvent) => void;
type FilteredKeys<T, U> = { [P in keyof T]: P extends string ? (T[P] extends U ? P : never) : never}[keyof T];
interface AComponentEvent extends JayCustomEvent {}
interface AComponent {
anEvent(e: AComponentEvent): void,
noParamFunction(): void,
someOtherFunction(a: number): void
someProp: string
}
let a: FilteredKeys<AComponent, EventHandler> = 'anEvent'
let b: FilteredKeys<AComponent, EventHandler> = 'noParamFunction'
let c: FilteredKeys<AComponent, EventHandler> = 'someOtherFunction'
let d: FilteredKeys<AComponent, EventHandler> = 'someProp'
I expect assignment a
to be working, while b
, c
and d
should not.
However, assignments a
and b
are valid, while only c
and d
are not.
Upvotes: 0
Views: 1273
Reputation: 46
I have solved exactly what I need, so posting here to help others.
Given the definitions
type Func0 = () => void
type Func1 = (x: any) => void
type DOMeventHandler<E> = (((this: GlobalEventHandlers, ev: E) => any) | null)
As @jsejcksn noted in his answer (which is correct), Func0
does extend Func1
. However, we can use this to construct a type that matches exactly the keys of an object who are one parameter functions.
The answer is here, following explanation
type EventHandlerKeys<T> = {
[P in keyof T]:
P extends string ?
(T[P] extends Func1 ?
(T[P] extends Func0 ? never : P) :
T[P] extends DOMeventHandler<any> ? P : never) :
never
}[keyof T];
The trick is to match on Func1
, which selects both functions of zero or one parameter. Then we match again on Func0
and return never
for zero parameter functions, and the actual property type for one parameter functions.
Here we also match on DOMEventHandler
as an additional bonus with another conditional branch.
Upvotes: 0
Reputation: 33739
Your mapping utility seems fine.
I think you might be surprised to learn that functions with lower arity are subtypes of compatible higher-arity ones. Consider the following example:
type AssignableTo<T, U> = T extends U ? true : false;
type Fn0Params = () => void;
type Fn1Param = (p: unknown) => void;
declare const ex1: AssignableTo<Fn0Params, Fn1Param>;
ex1 // true
type Fn0ParamsNever = (...args: never) => void;
declare const ex2: AssignableTo<Fn0ParamsNever, Fn1Param>;
ex2 // false
So, by typing your noParamFunction
as having parameters of type never
, you can prevent it from being assignable to EventHandler
:
noParamFunction(...args: never): void
Perpendicular to your question: If you're actually working with
CustomEvent
s, then you might want to use(event: CustomEvent<JayCustomEvent>) => void
Upvotes: 1