Home > Software design >  Filtering keyof in typescript mapped types works unexpectedly
Filtering keyof in typescript mapped types works unexpectedly

Time:12-19

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.

CodePudding user response:

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

TS Playground

Perpendicular to your question: If you're actually working with CustomEvents, then you might want to use (event: CustomEvent<JayCustomEvent>) => void

  • Related