We’re going to finish our journey on “real world usages” with some advanced types.
Unions and Intersection types
Intersection types
1interface BaseConfig { version: string; name: string; }
2
3interface DynamicConfig { fromFile: string; }
4
5interface StaticConfig { configuration: object; }
6
7type Configuration = (StaticConfig | DynamicConfig) **& BaseConfig**;
Here, a Configuration can have 2 different shapes that share the same BaseConfig base type.
1const config: Configuration = {
2 version: '1.0',
3 name: 'myDynamicConfig',
4 fromFile: './config.json'
5}; // this is a DynamicConfig
6
7const config: Configuration = {
8 version: '1.0',
9 name: 'myStaticConfig',
10 configuration: { ... }
11} // this is a StaticConfig
Remember that intersections type are useful for composing or overriding existing types.
The Discriminated Unions
Discriminated Unions is a pattern that allow to build types that shares a common property but have different shapes (example: redux actions).
- Types that have a common, singleton type property — the discriminant.
- Then, a type alias that takes the union of those types — the union.
- Finally, a type guard on the common property (on the discriminant).
Example
Here,
- The discriminant is the type property, shared by both Car and Moto types.
- The Vehicule type is the union type.
- Finally, the switch type guard is based on the type property (on the discriminant).
Based on the shape and on the
type discriminant property,
we know that a moto can have 1–2 passengers
(
[Person] | [Person, Person] tuples union type).
This pattern is really useful “in the real world”, a concrete example is:
- typing redux reducers (which are basically switches on a action type discriminant property).
Mapped types
While Generic types help you to create “General types”,
Mapped Types will help you transform existing types.
In a mapped type, the new type transforms each property in the old type in the same way.
But, how do we “transform each property”, in other terms, how do we iterate over a type properties?
Let’s take a look at the following keywords: in and keyof.
Imagine we have the following User type:
All those User properties are required on API side, however, in our single page application, the on-boarding is divided in 2 steps, meaning that the non-saved user record might have undefined properties, let’s call it theLocalUser type.
How to create this on-boarding LocalUser type?
One solution might be to create a copy of the User type with “nullable” properties:
However, for a maintainability point of view, it’s a shame to duplicate the User type.
This is when Mapped Types will help us:
LocalUser is just the User type with same optional properties.
Let’s look at this, step by step:
- keyof User will be a type that would be any value in key of User, so the following union type: ‘first_name’ | ‘last_name’ | ‘city’ | ‘twitter_handle’
- K in will give us an iterator of User properties union type.
- Finally, User[K], called a index type, will bring us the type of the K index of User.
In short, when K is city, then User[K] will be CityShortFormat.
Let’s end with a combination of mapped type and generics:
1type Partial<T> = { \[P **in** **keyof** T\]?: T\[P\]; }
2type LocalUser = Partial<User>;
We now have a general way to get a “partial subtype” of any type, here: User.
Conditionals
Conditional type enable building more complex types using the ternary operator syntax to perform a type resolution.
- Exclude<T, U> – “Exclude from T those types that are assignable to U.”
Let’s now look at Exclude<T, U> definition:
and usage:
Conditional Types “real world usages”?
While Conditional Types are a really advanced feature that might be useful only “to type”
general purpose libraries that use complex APIs (like
lodash or
recompose), knowing that it exists and how it works may be useful.
Especially depending on the complexity of your project.
Want to know more about Conditional Types?
This article only “scratch the surface” of the subject, for example, I don’t talk about the new infer keyword.
If you are interested in this subject, I really encourage you to read the following article that will introduce you this infer keyword and how to use it:
Conclusion
Every-time you end “copy-pasting” types or writing very long ones,
ask yourself:
Is there an advanced typing feature of TypeScript to help me having a more concise or powerful solution?
If you want to dive further into advanced types, I advise you to take a look at the TypeScript documentation — that contains deeper explanations and even more advanced concepts:
You prefer “learn by example”? Then, you should definitely take a look at the SimplyTyped repository that centralise dozen of advanced types: