TypeScript —
Generics and overloads
https://unsplash.com/photos/AnbW1pMD4kY
Photo by https://unsplash.com/photos/AnbW1pMD4kY
Generic types are essential for real-world usage.
This article is part of the collection “TypeScript Essentials”, this is the 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.

 

Why use Generic types?

Imagine we want to expose the following helper in our application:
1function withUID (obj) {   
2   return Object.assign({}, obj, { uuid: \_.uniqueId() });  
3}
The more straightforward way to “type” this function would be:
1function withUID (obj: any) {   
2   return Object.assign({}, obj, { uuid: \_.uniqueId() });  
3}
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** .
/images/publications/typescript-generics-and-overloads-1.png
By using scalar types (`object`, …) or any, we prevent TypeScript to infer the return type.
To overcome this problem, we’re gonna use generics.

 

Using Generic types

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:
1function withUID<T>(obj: T) {   
2   return Object.assign({}, obj, { uuid: \_.uniqueId() });  
3}
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.
1interface A<T, S> {  
2   a: T;  
3   b: S;  
4   c: { id: string } & S;  
5}
As you can see, we can define as many “type argument” as needed.
/images/publications/typescript-generics-and-overloads-2.png
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.

 

Enjoying this article so far?

 

Generics can “extends”

The type argument can provide some constraints by using the extends keyword.
1function withUID<T extends object>(obj: T) {   
2   return Object.assign({}, obj, { uuid: \_.uniqueId() });  
3}
4
5withUID({ a: 1 }); // is valid  
6withUID("hello"); // is **NOT** valid
Here, T should fulfill the condition “is an object type”.
1interface Person { name: string; }
2
3function withUID<T extends Person>(obj: T) {   
4   return Object.assign({}, obj, { uuid: \_.uniqueId() });  
5}
6
7withUID({ name: "POLY", surname: "Chack" }); // is valid

 

Generics can have a “default type value”

Finally, argument types can also have “default” or “computed value”.
1interface A<T=string> {  
2  name: T;  
3}
4
5const a:A = { name: "Charly" };  
6const a:A<number> = { name: 101 };
This is particularly important for interfaces which unlike function generics cannot omit type argument, example:
/images/publications/typescript-generics-and-overloads-3.png
/images/publications/typescript-generics-and-overloads-5.png
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”:
/images/publications/typescript-generics-and-overloads-6.png
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:
1function MyFunction<T extends Person, S=T&{ ssid: string }>(  
2   person: S  
3): S {  
4   /\* ... \*/  
5}

 

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?
1function getArray(...args) {  
2   if (args.length === 1 && typeof args\[0\] === 'number') {  
3      return new Array(args\[0\])  
4   } else if (args.length > 1) {  
5      return Array.from(args);  
6   }  
7}
8
9getArray(5) // => \[undefined x 5\]
10
11getArray('a', 'b', 'c') // => \['a', 'b', 'c'\]
12
Here is the inferred result type:
/images/publications/typescript-generics-and-overloads-7.png
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
/images/publications/typescript-generics-and-overloads-8.png
Overloads are the fact of listing all the possible input/output types couple for a given function.
ℹ️ Please note that :
  • overloads have no body
  • 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:
1class MyComponent extends React.Component {}
With TypeScript, the React.Component class refer to Component type.
/images/publications/typescript-generics-and-overloads-9.png
P stands for Props type and S for State type
Component type extends ComponentLifecycle — which provide all famous lifecycle methods:
/images/publications/typescript-generics-and-overloads-10.png
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!
1interface Props { user: User }  
2interface State {}
3
4class MyComponent extends React.Component<Props, State> {  
5   state: State = {}; // important!  
6     
7   // ...  
8}

 

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.
/images/publications/typescript-generics-and-overloads-11.png
All lodash functions provide this advanced and complete typing!

 

 

Last words

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!
We use cookies to collect statistics through Google Analytics.
Do not track
 
Allow cookies