Typescript Utility types
Utility types are helpers that typescript provides to make common type transformations easier.
For example if you have a Todo type
type Todo = {
readonly id: number;
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
readonly createdAtTimestamp: number;
updatedAtTimestamp: number | null;
};
And you want to create a TodoPreview type that only has title, status properties.
Instead of creating a new type by hardcoding it, you can use
type TodoPreview = Pick<Todo, "title" | "status">;
// type TodoPreview = { title: string; status: TaskStatus }
You might think "So why don't I just hardcode it?"
While hardcoding might work for small projects, it quickly becomes a Maintenance Nightmare in professional environments for two main reasons:
Intent & Readability: As in larger types you have to manually compare the two types to figure out how they are related while Utility types make the relationship clear
Scalability: As the codebase scales and requires changes constantly, keeping multiple hardcoded definitions in sync is a pain
this bring us to the core concept:
Single Source of Truth (SSOT)
For example if we have these types:
type Todo = {
readonly id: number;
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
readonly createdAtTimestamp: number;
updatedAtTimestamp: number | null;
};
type TodoPreview = {
title: string;
status: "done" | "in-progress" | "todo";
};
type TodoUpdate = {
title?: string;
description?: string;
category?: string;
status?: "done" | "in-progress" | "todo";
updatedAtTimestamp?: number | null;
};
And in a later version of the codebase we want to update status union type to "done" | "inProgress" | "todo", now you have to update each single definition for the status property inside each type. If you forget to update every single definition the types will become incompatible which will lead to bugs for sure
So we could prevent this type of problem by have Todo type as the only single source of truth and create the other types like this:
type Todo = {
readonly id: number;
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
readonly createdAtTimestamp: number;
updatedAtTimestamp: number | null;
};
type TodoPreview = Pick<Todo, "title" | "status">;
type TodoUpdate = Partial<Omit<Todo, "id" | "createdAtTimestamp">>;
Table of Content
| Type | Syntax | Brief |
|---|---|---|
| Omit | Omit<Type, Keys> | excludes specific properties |
| Pick | Pick<Type, Keys> | picks only specific properties |
| Partial | Partial<Type> | makes all properties optional |
| Required | Required<Type> | makes all properties required |
| Readonly | Readonly<Type> | makes all properties readonly |
| Record | Record<Keys, Type> | defines object types dynamically |
| Exclude | Exclude<UnionType, ExcludedMembers> | excludes members from a union type |
| Extract | Extract<Type, Union> | extracts members from a union type |
| NonNullable | NonNullable<Type> | excludes nullish from a type |
| Awaited | Awaited<Type> | resolves a promise type |
| Parameters | Parameters<Type> | extracts a function type parameters |
| ReturnType | ReturnType<Type> | extracts a function return type |
| ConstructorParameters | ConstructorParameters<Type> | extracts a constructor function type parameters |
| InstanceType | InstanceType<Type> | constructs the instance type of a constructor function type |
| OmitThisParameter | OmitThisParameter<Type> | removes the this declaration from a function type |
| ThisParameterType | ThisParameterType<Type> | extracts the this declaration from a function type |
| ThisType | ThisType<Type> | overrides the this declaration of a function |
| Uppercase | Uppercase<Type> | converts all string type characters to uppercase |
| Lowercase | Lowercase<Type> | converts all string type characters to lowercase |
| Capitalize | Capitalize<Type> | converts first character of a string type characters to uppercase |
| Uncapitalize | Uncapitalize<Type> | converts first character of a string type characters to lowercase |
Omit<Type, Keys>
takes an object of Type and excludes given keys from it Keys
type Todo = {
readonly id: number;
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
readonly createdAtTimestamp: number;
updatedAtTimestamp: number | null;
};
type NewTodoInput = Omit<
Todo,
"id" | "createdAtTimestamp" | "updatedAtTimestamp"
>;
/*
type TodoUpdate = {
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
}
*/
const createNewTodo = (todo: NewTodoInput) =>
saveToDB({
...todo,
id: Math.random(),
createdAtTimestamp: Date.now(),
updatedAtTimestamp: null,
});
createNewTodo({
title: "make article about utility types",
status: "in-progress",
});
Pick<Type, Keys>
creates a subset of object Type by including only given keys Keys
(the opposite of Omit)
type Todo = {
readonly id: number;
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
readonly createdAtTimestamp: number;
updatedAtTimestamp: number | null;
};
type TodoPreview = Pick<Todo, "title" | "status">; // type TodoPreview = { title: string; status: TaskStatus }
Partial<Type>
takes an object of Type and sets all its properties optional
type Todo = {
readonly id: number;
title: string;
description?: string;
category?: string;
status: "done" | "in-progress" | "todo";
readonly createdAtTimestamp: number;
updatedAtTimestamp: number | null;
};
type TodoUpdate = Partial<
Omit<Todo, "id" | "createdAtTimestamp" | "updatedAtTimestamp">
>;
/*
type TodoUpdate = {
title?: string;
description?: string;
category?: string;
status?: "done" | "in-progress" | "todo";
}
*/
const updateTodo = (id: Todo["id"], update: TodoUpdate) => ({
...getTodo(id),
...update,
});
updateTodo(3, { status: "done" });
Required<Type>
takes an object of Type and sets all its properties required
(the opposite of Partial)
type Options = {
opt1?: boolean;
opt2?: boolean;
opt3?: number;
};
const userOptions = {};
const defaultOptions: Required<Options> = {
opt1: true,
opt2: true,
opt3: 10,
...userOptions,
};
Readonly<Type>
takes an object of Type and sets its properties readonly (meaning the properties cannot be reassigned)
type Options = {
opt1?: boolean;
opt2?: boolean;
opt3?: number;
};
const userOptions: Readonly<Options> = {
opt1: true,
opt3: 10,
};
userOptions.opt2 = false;
// ~Error: Cannot assign to 'opt2' because it is a read-only property.
Record<Keys, Type>
defines an object type where every key is a Keys type and every value is a Type type
type GeneralObject = Record<keyof any, unknown>;
It can be used with union types to create an object type where every type in the union must have an associated value:
type UserRole = "admin" | "editor" | "guest";
type Permission = "create" | "delete" | "edit" | "browse";
type RolePermissions = Record<UserRole, Permission[]>;
/*
type RolePermissions = {
admin: Permission[];
editor: Permission[];
guest: Permission[];
}
*/
const role1: RolePermissions = {
admin: ["create", "delete", "edit"],
editor: ["edit", "browse"],
guest: ["browse"],
};
in case you want to have the same utility but as optional union keys, you can combine utility types!
type PartialRolePermissions = Partial<RolePermissions>;
/*
type PartialRolePermissions = {
admin?: Permission[] | undefined;
editor?: Permission[] | undefined;
guest?: Permission[] | undefined;
}
*/
const role2: PartialRolePermissions = {
admin: ["create", "delete"],
};
Exclude<UnionType, ExcludedMembers>
excludes members ExcludedMembers from a union type UnionType
type TodoStatus = "done" | "in-progress" | "todo";
type TodoUnCheckedStatus = Exclude<TodoStatus, "done">; // type TodoUnCheckedStatus = "in-progress" | "todo"
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
type T = Exclude<Shape, { kind: "circle" }>;
// type T = { kind: "square"; x: number } | { kind: "triangle"; x: number; y: number };
it excludes each member that includes or matches the given structure
so kind: "circle" works as unique identifier for the first union member just like y: number could be used as well to identify the third member kind: "triangle"
type T = Exclude<Shape, { y: number }>;
// type T = { kind: "circle"; radius: number } | { kind: "square"; x: number }
type T = Exclude<Shape, { x: number }>;
// type T = { kind: "circle"; radius: number };
Extract<Type, Union>
extracts (or picks) specific types that includes or matches the given structure Union from a larger union type Type
(the opposite of Exclude)
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
type T = Extract<Shape, { radius: number }>;
// type T = { kind: "circle"; radius: number };
works like a filter that extracts whatever has a radius
NonNullable<Type>
excludes null and undefined from a type Type
type T0 = NonNullable<string | number | undefined>;
// type T0 = string | number
type T1 = NonNullable<string[] | null | undefined>;
// type T1 = string[]
Awaited<Type>
takes a promise Type and represents whatever the promise resolves to
type Value = Awaited<Promise<string>>; // type Value = string
const value: Value = await Promise.resolve("hello");
Parameters<Type>
Extracts the types used in the parameters of a function type Type in a , this will be the return type of the last signature as typescript can't tell which signature is intended
declare function updateTask(id: number, data: TaskUpdate): Task;
type T0 = ReturnType<typeof updateTask>;
// type T0 = Task
ConstructorParameters<Type>
Extracts the types used in the parameters of a class constructor function type Type in a in Typescript
type DatabaseContext = {
db: { save: (data: string) => void };
};
function saveUser(this: DatabaseContext, user: string) {
this.db.save(user);
}
type SaveUserWithoutThis = OmitThisParameter<typeof saveUser>;
// type SaveUserWithoutThis = (user: string) => void
ThisParameterType<Type>
extracts the type of
SOCIAL SHARE CARD GENERATOR