I’m trying to create a type B which defines same function as type A but omits the last parameter. I’ve tried the following but it still asks for 2 parameters.
type Callback = (msg: string) => void;
type A = (A: string, B: Callback) => void;
type B = <U extends Exclude<Parameters<A>, Callback>>(...args: U) => void;
const b: B = (...args) => {};
b("some string"); // Expected 2 arguments, but got 1.
How can I do this properly?
>Solution :
You can use conditional type inference to exclude the last parameter from a function type:
type ExcludeLastParam<T extends (...args: any) => any> =
T extends (...args: [...infer I, any]) => infer R ? (...args: I) => R : never;
This matches T
against (...args: [...I, any]) => R
for some I
and R
that the compiler infers using the infer
keyword. Note that [...I, any]
is a variadic tuple type and by matching [...I, any]
against a tuple type (like, say, [a: string, b: Callback]
), the compiler will infer I
to be the "initial" part of the tuple excluding the last entry (since the any
type will match anything) (like, say, [a: string]
).
Let’s test it out:
type Callback = (msg: string) => void;
type A = (a: string, b: Callback) => void;
type B = ExcludeLastParam<A>;
// type B = (a: string) => void
And
type C = (a: string, b: number, c: boolean, d: Date) => string;
type D = ExcludeLastParam<C>
// type D = (a: string, b: number, c: boolean) => string
Looks good!
Note that your version doesn’t work because, while the Parameters<T>
utility type can be used to extract the parameter list as a tuple type, the Exclude<T, U>
utility type does not filter tuple types; instead it filters union types. Since Parameters<A>
is of type [a: string, b: Callback]
, filtering out Callback
isn’t going to do anything (because Parameters<A>
is not a union… or maybe it’s a single member union, and that member is not assignable to Callback
).