Creating Custom Utility Types in TypeScript
While TypeScript provides many built-in utility types (like Partial, Pick, Omit), you often need to create your own to handle specific type transformations in your application. Custom utility types are essentially generic type aliases that use advanced features like Mapped Types, Conditional Types, and infer.
1. The Basics: Generic Type Aliases
A utility type is just a type that takes another type as an argument.
// A simple utility that makes a type nullable
type Nullable<T> = T | null;
const s: Nullable<string> = "hello"; // string | null
2. Using Mapped Types
Mapped types allow you to iterate over keys of an object.
Example: Mutable<T>
Removes readonly modifier from all properties.
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
interface Locked {
readonly id: number;
}
type Unlocked = Mutable<Locked>; // { id: number }
3. Using Conditional Types
Conditional types allow you to filter or select types based on logic (T extends U ? X : Y).
Example: Diff<T, U>
Removes types from T that are present in U.
type Diff<T, U> = T extends U ? never : T;
type Letters = "a" | "b" | "c";
type RemoveC = Diff<Letters, "c">; // "a" | "b"
4. Using infer
The infer keyword allows you to extract a type from within another type.
Example: UnwrapPromise<T>
If T is a Promise, extract the type inside. Otherwise, return T.
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<number>; // number
Example: ArgumentTypes<T>
Extract arguments of a function as a tuple.
type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;
function greet(name: string, age: number) {}
type Params = ArgumentTypes<typeof greet>; // [string, number]
5. Combining Techniques (Key Remapping)
Real-world utility types often combine these features. For example, picking properties based on their value type.
type PickByType<T, ValueType> = {
[K in keyof T as T[K] extends ValueType ? K : never]: T[K];
};
interface User { id: number; name: string; isAdmin: boolean; age: number; }
// Only keep number properties: { id: number; age: number; }
type NumberProps = PickByType<User, number>;
programming/javascript/typescript/typescript programming/javascript/typescript/advanced-types programming/javascript/typescript/utility-types