Conditional Types in Typescript
Conditional types are types that are derived based on a condition, they are derived types that depend on the truthiness of a condition
By the end of this article, you should:
- Understand the syntax and usage of conditional types in TypeScript
- Learn how to write more generic and reusable code using conditional types
- Apply conditional types to real-world scenarios in your own TypeScript code
- Gain a deeper understanding of TypeScript’s type system and how it can help you write safer, more maintainable code.
Conditional types are types that are derived based on a condition, they are derived types that depend on the truthiness of a condition. A conditional statement takes the form condition ? trueExpression: falseExpression
.
To create a new conditional type in typescript, it takes the form
type NewType = Type extends OtherType ? TruthyType : FalsyType
In a conditional type, the condition plays a crucial role in determining whether the type evaluates to true or false. This is why it is important to understand how it works.
The Condition
The condition is the part that is checked, and if it meets the requirement, the statement will evaluate to true
, else false
. In the snippet above, Type extends OtherType
is the condition, which means if OtherType
is a subset or is within Type
, the [extends
keyword is used to constraint types](https://medium.com/@developer.olly/typescript-extends-keyword-explained-101b1636c8e4) , for example
string extends string // true
string extends number // false
any extends number // true
never extends string // false
type Car = {
name: string,
model: string,
make: string,
year: number,
engine: string,
}type Name = {name: string}Car extends Name // true
Car extends {legs: number} // false
In the first section of the above, using the extends
keyword, the code checks whether one type extends another or is in the other type or a subset of the other type. For instance, string extends string
evaluates to true because string belongs to string, while string extends number
evaluates to false because there is no union between these 2 types.
In the second section, there are two object types defined: Car
and Name
. The Car
object type has several properties, including name
, model
, make
, year
, and engine
. The Name
object type has only one property, name
.
Car extends Name
evaluates to true
, since Car
has a name
property that matches the name
property in Name
. On the other hand, Car extends {legs: number}
evaluates to false
, since Car
does not have a legs
property.
It is worth noting that the condition can vary depending on the type we are working with. For instance, if we are dealing with an object type like Car
in the above snippet, the OtherType
in the condition may be based on the properties(name
) of the object. On the other hand, if we are working with a function type, the condition may be based on the arguments and the return type of the function(more on this later). Therefore, it is important to carefully analyze the type we are working with in order to understand the condition that needs to be met.
Generic Conditional Types
Conditional types shine more with generics, generics in Typescript are similar to parameters in functions. They allow us to define a type or a set of types that can be used in multiple places in our code. This helps us write more reusable and generic code. However, oftentimes, we might want to create a new and flexible type based on a Generic, For example, let’s look at this snippet.
type isNumber<T> = T extends number ? true : false.
The isNumber
type takes a generic parameter T
and checks whether T
extends the number
type. If it does, the type evaluates to true
; otherwise, it evaluates to false
. For example, isNumber<32>
would evaluate to true
, while isNumber<"hello">
would evaluate to false
.
Let’s look at a typical typescript example Flatten<T>
but we call it UnArray<T>
,
type UnArray<T> = T extends any[] ? T[number] : T
type Users = {
name: string,
age: number,
city: string
}[]type User = UnArray<Users>
/** {
name: string,
age: number,
city: string
} */type Str = UnArray<string> // string
This code defines a generic type UnArray
that takes a type T
as a parameter. The conditional type checks whether T
extends any[]
(an array of any type), and if it does, it returns the element type of the array using the T[number]
syntax. If T
does not extend any[]
, it returns T
as is.
In the example code, Users
is a type that defines an array of objects with name
, age
, and city
properties. User
is defined as the result of applying the UnArray
type to Users
, which returns the object type within the array of Users
. Str
is defined as the result of applying the UnArray
type to string
, which returns string
since it is not an array.
In general, the UnArray
type can be used to "flatten" nested arrays, taking an array of arrays and returning a single array of the same element type.
One more example, from the Solid-three code snippet,
type Args<T> = T extends new (...args: any) => any
? ConstructorParameters<T>
: T;
Args
is a generic type that takes a type T
as a parameter. The conditional type checks whether T
extends a constructor function that takes any number of arguments ...args
and returns any type. If the condition is true, then the type evaluates to the tuple of argument types [ConstructorParameters<T>
](https://www.typescriptlang.org/docs/handbook/utility-types.html#constructorparameterstype) of this constructor function. Otherwise, it evaluates to T
.
For instance, given a constructor function User
that takes two arguments of type string
and boolean
, respectively, we can use Args<MyClass>
to extract the tuple type of its arguments:
class User {
constructor(name: string, isAdult: boolean) {}
}
type UserArgs = Args<typeof User>; // [user:string, isAdult: boolean]
In this example, UserArgs
is inferred as the tuple type [string, boolean]
, since User
takes two arguments of these types in its constructor function.
In Summary
This article provides a brief guide to conditional types in TypeScript. we explained what conditional types are and how to create new conditional types in TypeScript, using the syntax Type extends OtherType ? TruthyType : FalsyType
. We also show how to use conditional types with generics, to write more generic and reusable code. also explains how the condition plays a crucial role in determining whether the type evaluates to true or false and provides several examples to illustrate how to apply conditional types to real-world scenarios in TypeScript code with some concrete examples.