Welcome to the second post in our series about TypeScript generics! In this post, we’ll take a closer look at TypeScript inference. We’ll discuss topics like advanced inference, mapped, and conditional types.
If you haven’t already, you should read the first part of the series about the basics of generics to follow along better.
Advanced inference in TypeScript
By combining generics with type inference, we can create advanced types built on top of each other. By creating new types in this way, you can create a robust type system for your project that is easy to maintain because all the children types will automatically adopt the change in their parents.
You may find the syntax for advanced type inference a bit tricky at first, but with a little practice, you should be able to grasp it.
Let’s start with the mapped types.
Mapped types derive from other types. When we combine mapped types with generics, we can form powerful and flexible type definitions.
Creating new types using the
keyof is one example. In
the first post
, we briefly touched on the
keyof. Now let’s explore it more in-depth.
keyof is a TypeScript operator that lets you derive new types based on the properties of others. It takes an input type and returns a new one with all of the properties of the original.
Here’s an example:
keyof in a generic type
KeysOfObject that we can use to create other mapped types. We use
KeysOfObject to create a new
UserKeys type based on the properties of
We get an error on line
accessUser(user, "SSN"). The reason for this is that we used the type
UserKeys to make sure our function only accepts existing user fields.
Creating mapped types is such a fundamental feature that TypeScript added utility types to the language. These utility types like Partial, Readonly, Pick and others make it easier to create custom mapped types.
These utility types use generics under the hood to work with the types they receive as the input. Let’s cover these three utility types in more detail.
Partial is used to create a new type with all of the fields set to optional. It’s great for working with an incomplete instance of the object where you don’t know ahead of time which fields may be missing:
Since we’re setting our
body param to type
Partial<User>, we don’t have to provide the whole user object to this function, only the fields we’re trying to update.
Readonly utility type is used to create a new type with all of the fields set to read-only. TypeScript won’t allow you to change any field value on such objects after the initialization:
Here, we’re creating a new type
Readonly<User>, with the same fields as the User type, except with read-only permissions. When we create a new instance of the
readonlyUser and try modifying it, TypeScript throws an error.
Pick utility is used for creating a new type by indicating which fields you wish to copy. To choose the fields, you pass them as a union type:
See my other post on the most useful utility types for React to learn more about utility types and how they can help you.
Turns out, we can use the same syntax to define types. TypeScript’s conditional types allow us to return a different type based on the value of the input type we receive:
The code above defines a conditional type,
BarkOrMeow<T>, which returns a type with either
meowSound depending on the input
T. Then we create a
CatSound type by passing the
Cat type to
Even with this trivial example, you can see how useful TypeScript conditional types can be.
Distributed conditional types
When defining conditional types, instead of returning a single type as part of our conditional statement, we can return several distributed types.
Distributed types allow you to add another level of flexibility to your code and handle more complex edge cases:
In the example above, we’re using distributed conditional type
dateOrNumberOrString to enforce the type for the second parameter of our
compareValues function. If the
value1 is a
Date, we want
value2 to also be a
value1 is a number, we want
value2 to be either Date or a number.
Conditional type inference
A more complex case for conditional types is inferring the new type as a part of the conditional statement. We can use the
infer keyword to infer the new type based on a certain property or signature of the input we receive.
This might sound a bit too abstract without a concrete example, so let’s take a look at one:
In this example, we infer type
U from the
id field of type
T has the
id property, TypeScript infers the type of that property as
U. You can then use
U within the same conditional type statement.
Conditional type inference allows creating a robust type checking logic that can deal with deeply nested objects.
Type inference from function signatures
In the same fashion that we infer types from object fields, we can also infer types from function signatures.
We can infer types from function parameters as well as from function return types:
Here we have two generic types that are inferring types from input function types. The first one uses the function parameters and the other function’s return type.
Let’s take a look at how you would use one of these types in action:
executeFunction function that uses
inferFromFunctionParam generic type we created earlier on its second argument
The first argument of
executeFunction is a function that takes one parameter,
param. TypeScript will infer the type of the second argument
arg from the type of
In other words, if
fn is supposed to receive a string, TypeScript ensures we can only pass a string as a value for the second
arg parameter of
If we tried to call
executeFunction with a number, TypeScript would throw an error:
The ability to enforce types based on the function signature of function parameters you receive allows for a whole other level of type safety.
In this post, we covered advanced type inference and combined it with generics to build flexible types on top of other ones. With a strong grasp on generics and type inference, we can ensure all of the data that flows through our app has strong type safety.
And that concludes our deep dive into TypeScript generics. Again if you haven’t read the first part yet, I encourage you to do so as it lays the foundation for understanding TypeScript generics .
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.