Overloading means that a function can have different parameters and return values, that is, different function signatures.

Ts supports function overloading and can define multiple different types for the same function:

There are three ways to write overloads (most people probably know only one) :

declare function func(name: string) :string;
declare function func(name: number) :number;
Copy the code

If you declare two functions with the same name, you can override them:

Functions can be declared as interface, and function overloading can also be declared as interface:

The type of a function can be a cross type, which means that multiple types can be used.

Overloading is a useful feature, but sometimes it can be cumbersome to write.

For example, ts provides lib.dom.ts with a type definition like this:

Because each argument has a different return value, it overloads that much.

It’s too much trouble to write, but can you use type programming to generate it dynamically?

Declare and interface don’t work, but & do. Can I pass in a union type and it returns a cross type?

Like this:

It must be possible, let’s look at how to implement it:

Combined cross

Function parameters have contravariant properties, that is, type reduction, such as parameters can be transmitted at the same time A, B, C, how to define the parameter type?

It must be the intersection of A, B, and C, i.e. the intersection of A, B, and C, so that it can accept A, B, and C parameters.

You can use this property to implement a union cross.

type UnionToIntersection<U> = 
    (U extends U ? (x: U) = > unknown : never) extends (x: infer R) => unknown
        ? R
        : never
Copy the code

Test it out:

Here the type parameter U is the union type passed in, and the U extends U is added to trigger the distributed conditional type property.

What are distributed condition types?

When a type parameter is a union type and the type parameter is referenced directly to the left of the condition type, TypeScript passes in each element individually for typing and then merges it into the union type. This syntax is called distributed conditional typing.

For example, a union type like this:

type Union = 'a' | 'b' | 'c';
Copy the code

If we want to capitalize the a, we can write it like this:

type UppercaseA<Item extends string> = 
    Item extends 'a' ?  Uppercase<Item> : Item;
Copy the code

Back to this advanced type of union cross:

The addition of U extends U or U extends any triggers the distributed conditional type feature, which allows the union types to be broken up into individual types for calculation, and then the result to be combined into the union type.

A function type is constructed by placing it at the position of function parameters. The parameter type is extracted by pattern matching and returned to the local variable R declared by Infer.

The result is a cross type.

The reason, as mentioned above, is that function arguments have contravariant properties, and passing in a union type returns a cross type.

Once union cross is implemented, function overloading can also be written:

For example, three overloads return Aaa, Bbb, and Ccc:

We want to pass in the union type to return the overloaded function based on the type definition that generates the overload:

You can write:

type UnionToOverloadFunction<T extends keyof ReturnValueMap> = 
    UnionToIntersection<
        T extends any ? (type: T) = > ReturnValueMap[T] : never
    >;
Copy the code

The type parameter T is the key in ReturnValueMap and the constraint is Keyof ReturnValueMap.

T extends any triggers the distribution of union types in distributed conditional types, passing in ‘AAA’, ‘BBB’, and ‘CCC’ separately for computation to return the constructed union of function types.

Let’s test this section separately:

You can see that the combined type of the constructed function type is returned.

Then use the UnionToIntersection above to do it:

This implements the dynamic generation of overloaded functions:

Compare that to the way we started:

Isn’t it refreshing? And you can write some dynamic logic.

conclusion

There are three types of ts overloading: declare function, interface, and interleaved &.

When there are a lot of overloads, it is more cumbersome to list them directly, so you can use type programming to generate function overloads on the fly.

We implemented the associative cross, taking advantage of the contravariant property of function parameters, which returns the cross type of the parameters when they may be multiple types.

Then, by taking advantage of the nature of distributed conditional type, when the union type is passed in, the type is passed in separately for calculation, and finally the result is merged into the union type.

Using this method, the union type of the function constructed by passing in the union type is realized, and then the dynamic generation of function overloads is realized by combining the union cross.

When you’re writing too many overloads, try using type programming for dynamic generation.