Advanced Type Patterns in TypeScript
Advanced type patterns enable sophisticated type manipulation. This article covers utility types, conditional types, and advanced inference.
Introduction
Advanced type patterns provide:
- Type manipulation
- Code reusability
- Type safety
- Flexibility
- Maintainability
Understanding these helps you:
- Create powerful types
- Build reusable utilities
- Maintain type safety
- Reduce code duplication
- Build scalable systems
Utility Types
Common Utility Types
// โ
Good: Partial - make all properties optional
type User = { name: string; age: number; email: string };
type PartialUser = Partial<User>;
// { name?: string; age?: number; email?: string }
// โ
Good: Required - make all properties required
type RequiredUser = Required<PartialUser>;
// { name: string; age: number; email: string }
// โ
Good: Readonly - make all properties readonly
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; readonly email: string }
// โ
Good: Pick - select specific properties
type UserPreview = Pick<User, 'name' | 'email'>;
// { name: string; email: string }
// โ
Good: Omit - exclude specific properties
type UserWithoutEmail = Omit<User, 'email'>;
// { name: string; age: number }
// โ
Good: Record - create object with specific keys
type Status = 'active' | 'inactive' | 'pending';
type StatusCount = Record<Status, number>;
// { active: number; inactive: number; pending: number }
// โ
Good: Exclude - exclude types from union
type NonString = Exclude<string | number | boolean, string>;
// number | boolean
// โ
Good: Extract - extract types from union
type StringOrNumber = Extract<string | number | boolean, string | number>;
// string | number
// โ
Good: ReturnType - get function return type
function add(a: number, b: number): number {
return a + b;
}
type AddReturn = ReturnType<typeof add>;
// number
// โ
Good: Parameters - get function parameter types
type AddParams = Parameters<typeof add>;
// [a: number, b: number]
// โ
Good: InstanceType - get class instance type
class User {
name: string;
}
type UserInstance = InstanceType<typeof User>;
// User
Conditional Types
Basic Conditional Types
// โ
Good: Basic conditional type
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
// โ
Good: Conditional with union
type Flatten<T> = T extends Array<infer U> ? U : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number
// โ
Good: Nested conditionals
type DeepFlat<T> = T extends Array<infer U>
? U extends Array<infer V>
? V
: U
: T;
type Result = DeepFlat<number[][]>; // number
Conditional Type Inference
// โ
Good: Infer in conditional types
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Num = GetReturnType<() => number>; // number
type Str = GetReturnType<() => string>; // string
// โ
Good: Extract function parameters
type GetParams<T> = T extends (...args: infer P) => any ? P : never;
type Params = GetParams<(a: string, b: number) => void>;
// [a: string, b: number]
// โ
Good: Extract Promise type
type Awaited<T> = T extends Promise<infer U> ? U : T;
type PromiseString = Awaited<Promise<string>>; // string
type PlainNumber = Awaited<number>; // number
Mapped Types
Basic Mapped Types
// โ
Good: Basic mapped type
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type User = { name: string; age: number };
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }
// โ
Good: Setters
type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type UserSetters = Setters<User>;
// { setName: (value: string) => void; setAge: (value: number) => void }
// โ
Good: Readonly mapping
type ReadonlyProps<T> = {
readonly [K in keyof T]: T[K];
};
type ReadonlyUser = ReadonlyProps<User>;
// { readonly name: string; readonly age: number }
Advanced Mapped Types
// โ
Good: Conditional mapped type
type Getters<T> = {
[K in keyof T as T[K] extends Function ? never : `get${Capitalize<string & K>}`]: () => T[K];
};
// โ
Good: Filter properties by type
type StringPropertiesOnly<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
type User = { name: string; age: number; email: string };
type UserStrings = StringPropertiesOnly<User>;
// { name: string; email: string }
// โ
Good: Create getters for specific types
type GettersForStrings<T> = {
[K in keyof T as T[K] extends string ? `get${Capitalize<string & K>}` : never]: () => T[K];
};
type UserStringGetters = GettersForStrings<User>;
// { getName: () => string; getEmail: () => string }
Template Literal Types
Basic Template Literal Types
// โ
Good: Template literal types
type EventName = `on${Capitalize<'click' | 'change' | 'submit'>}`;
// 'onClick' | 'onChange' | 'onSubmit'
// โ
Good: Generate property names
type Getters<T extends Record<string, any>> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// โ
Good: Create event handlers
type EventHandlers<T extends Record<string, any>> = {
[K in keyof T as `on${Capitalize<string & K>}`]: (value: T[K]) => void;
};
type User = { name: string; age: number };
type UserHandlers = EventHandlers<User>;
// { onName: (value: string) => void; onAge: (value: number) => void }
Type Inference Patterns
Advanced Inference
// โ
Good: Infer from object structure
type InferObject<T> = T extends { data: infer D } ? D : never;
type Result = InferObject<{ data: string; status: number }>;
// string
// โ
Good: Infer from array
type InferArray<T> = T extends (infer U)[] ? U : never;
type Item = InferArray<string[]>;
// string
// โ
Good: Infer from function
type InferFunction<T> = T extends (...args: any[]) => infer R ? R : never;
type Result = InferFunction<(a: number) => string>;
// string
// โ
Good: Deep inference
type DeepInfer<T> = T extends { nested: { value: infer V } } ? V : never;
type Value = DeepInfer<{ nested: { value: number } }>;
// number
Practical Advanced Patterns
Type-Safe API Builder
// โ
Good: Type-safe API builder
type ApiEndpoint = {
path: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
request?: Record<string, any>;
response: Record<string, any>;
};
type ApiBuilder<T extends Record<string, ApiEndpoint>> = {
[K in keyof T]: (
data: T[K]['request'] extends undefined ? void : T[K]['request']
) => Promise<T[K]['response']>;
};
const endpoints = {
getUser: {
path: '/users/:id',
method: 'GET' as const,
response: { id: number; name: string }
},
createUser: {
path: '/users',
method: 'POST' as const,
request: { name: string; email: string },
response: { id: number; name: string; email: string }
}
};
type Api = ApiBuilder<typeof endpoints>;
// {
// getUser: (data: void) => Promise<{ id: number; name: string }>;
// createUser: (data: { name: string; email: string }) => Promise<{ id: number; name: string; email: string }>;
// }
Type-Safe Form Builder
// โ
Good: Type-safe form builder
type FormField = {
type: 'text' | 'number' | 'email' | 'checkbox';
required?: boolean;
validate?: (value: any) => boolean;
};
type FormSchema = Record<string, FormField>;
type FormValues<T extends FormSchema> = {
[K in keyof T]: T[K]['type'] extends 'checkbox'
? boolean
: T[K]['type'] extends 'number'
? number
: string;
};
const userForm = {
name: { type: 'text' as const, required: true },
age: { type: 'number' as const },
email: { type: 'email' as const, required: true },
subscribe: { type: 'checkbox' as const }
};
type UserFormValues = FormValues<typeof userForm>;
// {
// name: string;
// age: number;
// email: string;
// subscribe: boolean;
// }
Best Practices
-
Use utility types:
// โ Good type PartialUser = Partial<User>; // โ Bad type PartialUser = { name?: string; age?: number }; -
Leverage conditional types:
// โ Good type Flatten<T> = T extends Array<infer U> ? U : T; // โ Bad type Flatten<T> = T extends Array<any> ? any : T; -
Use mapped types for consistency:
// โ Good type Getters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; }; // โ Bad type Getters<T> = { getName: () => T['name']; getAge: () => T['age']; };
Common Mistakes
-
Over-complicating types:
// โ Bad type Complex<T> = T extends any ? T : never; // โ Good type Simple<T> = T; -
Not using infer:
// โ Bad type GetReturn<T> = T extends (...args: any[]) => any ? any : never; // โ Good type GetReturn<T> = T extends (...args: any[]) => infer R ? R : never;
Summary
Advanced type patterns are powerful. Key takeaways:
- Use utility types
- Leverage conditional types
- Use mapped types
- Apply template literals
- Infer types effectively
- Build type-safe APIs
- Maintain readability
- Document complex types
Related Resources
Next Steps
- Learn about TypeScript Best Practices
- Explore Generics
- Study Decorators
- Practice advanced patterns
- Build type-safe systems
Comments