Extending another TypeScript function with additional arguments

Viewed 10346

Is it possible in to define a function type and extend its argument list in another type (overloading function type?)?

Let's say I have this type:

type BaseFunc = (a: string) => Promise<string>

I want to define another type with one additional argument (b: number) and the same return value.

If at some point in the future BaseType adds or changes arguments this should also be reflected in my overloaded function type.

6 Answers

UPD: it's possible to extend a function's parameters in TypeScript. See Titian's answer.

Original answer: I doubt that it can be done in the way you described. However, you can try something like this:

interface BaseOptions {
    a: string;
}
type BaseFunc = (options: BaseOptions) => Promise<string>

interface DerivedOptions implements BaseOptions {
    b: number;
} 
type DerivedFunc = (options: DerivedOptions) => Promise<string>

Another advantage of this approach is that you have named parameters for free. So it's going to be cleaner from a caller side than just calling BaseFunc or DerivedFunc with positional arguments. Just compare:

someFuncA(1, undefined, true);

// vs

someFuncB({nofiles: 1, enableLogging: true});  // and bar: undefined is just omitted

You could use spread with Parameters<>:

function base(a: number, b: number, c: number): number
function derived(add: string, ...base: Parameters<typeof base>): number

The restriction is that base parameters have to be on the end.

You can make any additional arguments optional.

class Plant {
  grow(inches: number) {}
}

class Grass extends Plant {
  // adding new optional argument in override
  grow(inches:number, cutOff?: boolean) {}
}

class CrabGrass extends Grass {
  // At this level of overriding, you no longer need it to be optional
  grow(inches:number, cutOff: boolean) {}
}

So, for those that ended up on this page just looking to quickly add another definition to your function interface:

Intersection type in Typescript

A & in TS in the context of a types means an intersection type. It merges all properties of 2 object types together and creates a new type

Example:

const SomeComponent: React.FC<Omit<ISomeInterface, 'arg1' | 'arg2' | 'arg3'>
 & { oneMoreArgument: string; }> = ({ arg1, arg2, arg3, oneMoreArgument }) => {

... function stuff

}

We can make use of the powerful utility types, so we don't even have to parametrize the initial function parameters ourselves, as this can be particularly problematic and time-consuming in more complex types.

I'll showcase a real-life scenario where I recently had to make use of this pattern. Consider the following function, which was opened by a click of <button>

openPopup = (event, isNotif) => { /*...*/ }
  • Now issue is, we can't manually type event: MouseEvent<HTMLButtonElement> because the interface MouseEvent does not accept any generics, producing a error.

  • We can however type our function as MouseEventHandler<HTMLButtonElement>

  • This however still does not solve our issue, as MouseEventHandler does not expect a custom second argument, yet another error.

We can however create a derived function handler in the following way:

type PopupHandler = (
  event: Parameters<MouseEventHandler<HTMLButtonElement>>[0]
  isNotif: boolean
) => ReturnType<MouseEventHandler<HTMLButtonElemement>>

// now it works!
openPopup: PopupHandler = (event, isNotif) => { /*...*/ }
  • We use Parameters utility type, to infer the event type as the first argument out of the args array
  • We use ReturnType to keep the proper type of return type.

This way we can actually extend (overload) an existing function type with our custom parameter while retaining the return type and maintaining proper types all throughout.

Related