Typescript — Generics, and overloads 👨‍🔬

Charly Poly
May 19, 2018 · 6 min read
https://unsplash.com/photos/AnbW1pMD4kY

This article is part of the collection “TypeScript Essentials”, this is Chapter four.


In the last article “TypeScript — Learn the basics”, we saw all basic usages of TypeScript types.

By the end of this article, you’ll understand why generic types are essential for real-world usage.


Generic — Configurable types

While basic types like interfaces are useful to describe data and basic functions signatures, generics helps making types “open” and reusable.

Imagine we want to expose the following helper in our application:

function withUID (obj) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
}

The more straightforward way to “type” this function would be:

function withUID (obj: any) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
}

We use any because we want to accept any value — indeed we can use object.

The problem is that the inferred return type of the function is any .

By using scalar types (object, …) or any,
we prevent TypeScript to infer the return type.

To overcome this problem, we’re gonna use generics.

In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is generics, that is, being able to create a component that can work over a variety of types rather than a single one.

https://www.typescriptlang.org/docs/handbook/generics.html

As explained this excerpt of the TypeScript documentation, we want to create functions that accept many kinds of types, without losing type — without using any.

Let’s see the withUID using generics:

function withUID<T>(obj: T) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
}

The <> syntax is reserved for describing generic type.
A generic expose many “type arguments”, listed between the <>.

Generics can be applied to interfaces , class and function.

interface A<T, S> {
a: T;
b: S;
c: { id: string } & S;
}

As you can see, we can define as many “type argument” as needed.

Here, the T type is inferred from the passed argument type.

If no type argument type is explicitly passed, TypeScript will try to infer them by the values passed to the function arguments.

Example, for withUID, T is inferred from the type of obj argument.

The type argument can provide some constraints by using the extends keyword.

function withUID<T extends object>(obj: T) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
}
withUID({ a: 1 }); // is valid
withUID("hello"); // is NOT valid

Here, T should fulfill the condition “is an object type”.

interface Person { name: string; }function withUID<T extends Person>(obj: T) { 
return Object.assign({}, obj, { uuid: _.uniqueId() });
}
withUID({ name: "POLY", surname: "Chack" }); // is valid

Finally, argument types can also have “default” or “computed value”.

interface A<T=string> {
name: T;
}
const a:A = { name: "Charly" };
const a:A<number> = { name: 101 };

This is particularly important for interfaces which unlike function generics cannot omit type argument, example:

For interfaces, TypeScript cannot infer type arguments based on properties value, unlike for functions

That’s why “default type value” is a “nice to know”:

This is correct.

Tips — default type arguments can reuse other type arguments

The example below describe how we can define a type of argument on-top of another:

function MyFunction<T extends Person, S=T&{ ssid: string }>(
person: S
): S {
/* ... */
}


Overloads — Extendable Function types

Generics are convenient for the functions that are responsible for simple isolated computation or predictable return value for given a input.

Unfortunately, everything isn’t this simple, some “big” functions may have high complexity with the variant return type.
Generics can be used in combination with “overloads” to overcome this issue.

For example, how can we type this function?

function getArray(...args) {
if (args.length === 1 && typeof args[0] === 'number') {
return new Array(args[0])
} else if (args.length > 1) {
return Array.from(args);
}
}
getArray(5) // => [undefined x 5]getArray('a', 'b', 'c') // => ['a', 'b', 'c']

Here is the inferred result type:

The solution is to use “Overloads”.

The answer is to supply multiple function types for the same function as a list of overloads. This list is what the compiler will use to resolve function calls.

https://www.typescriptlang.org/docs/handbook/functions.html

Overloads are the fact of listing all the possible input/output types couple for a given function.

⠀⠀

ℹ️ Please note that :

  • overloads have nobody
  • the implementation function must have as open as possible typing
    (to allow overloading)

Real-world examples

interface with Generic example: React.Component

You probably know how to declare a React Component in ES6:

class MyComponent extends React.Component {}

With TypeScript, the React.Component class refer to Component type.

P stands for Props type and S for State type

Component type extends ComponentLifecycle — which provide all famous lifecycle methods:

You can see here how React use Generics to propagate Props and State types to class methods

So, if you use React with TypeScript, remember to provide type arguments for Props and State!

interface Props { user: User }
interface State {}
class MyComponent extends React.Component<Props, State> {
state: State = {}; // important!

// ...
}

Overloads example: lodash _.filter()

_.filter can take different types of arguments as value, which results in a pretty complex mix of Generic with Overloads typing.

All lodash functions provide this advanced and complete typing!


I hope you now understand the concepts of Generic and Overloads and especially why it’s important is a real-world TypeScript project.

If you want to dive further and level-up your typing skills, I advise you to take a look at the following typings :

The next chapter —“Typescript — Super-types 💪”— will cover more complex cases with more real-world examples 👷



Latest Serie is out!

Interested in React? You should check-out my last publication:


Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store