Distributive Conditional Types in TypeScript
A quick look at how TypeScript conditional types behave with union types.
I am digging deeper into TypeScript which reminds me of my Haskell days. It has been a while and I don't remember much Haskell now, but the good feeling stays and so do my static type skills which I promptly decided to exercise now with TypeScript.
Unions
We can combine any two types into a union type.
type StringOrNumber = string | number;Or with TypeScript's literal types:
type UserRole = "employee" | "admin" | "superadmin";Generics
Assume we have entities like User, Company, Course, etc., we can create an ApiResponse:
type ApiResponse = {
data: User | Company | Course;
status: number;
}But then every time we add a new entity in our system, we also need to update the ApiResponse. Instead we can use generics:
type ApiResponse<T> = {
data: T;
status: number;
}And then use it like ApiResponse<User>, ApiResponse<Company>, ApiResponse<Course>.
Conditional Types
For example, if we have types:
type Employee = {...};
type Admin = Employee & {...};
type SuperAdmin = Admin & {...};We can create separate permissions for all admins (which includes Admin and SuperAdmin) using conditional types.
type Permissions<T> = T extends Admin ? AdminPermissions : EmployeePermissions;Distributive Conditional Types
Type parameters can be instantiated with any type, including unions:
type Users = Array<User | Admin | SuperAdmin>;Now Users is of type (User | Admin | SuperAdmin)[]. Which means it is an array whose elements could be a mix of any of those three types of users. But what if for some reason we want the type to represent one homogeneous array shape instead? That is, User[] | Admin[] | SuperAdmin[].
Essentially we want to "loop" over the union (User | Admin | SuperAdmin) and make each type an array. Conditional types have this "looping" behavior, formally called distributivity. We can use this to our benefit here:
type StrictArray<T> = T extends unknown ? T[] : never;
type Users = StrictArray<User | Admin | SuperAdmin>;That T extends unknown seems redundant, but it is a dummy conditional that activates distributivity. Because T appears directly on the left side of extends, TypeScript applies the conditional to each member of the union, giving us our desired type User[] | Admin[] | SuperAdmin[].
Available for hire
I am open to new roles. I bring 13 years of backend engineering experience, primarily in Ruby on Rails, with additional experience in TypeScript, Node.js, and Elixir.
Please have a look at my services.
Leads and referrals welcome — tejasbubane@gmail.com