Advanced TypeScript Type System
TypeScript's type system is incredibly powerful. Mastering generics, conditional types, and template literals eliminates entire classes of bugs.
Generics with Constraints
// Generic with constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name"); // type: string
// Multiple constraints
interface Serializable { serialize(): string; }
interface Identifiable { id: string; }
function saveEntity<T extends Serializable & Identifiable>(entity: T): void {
const data = entity.serialize();
database.save(entity.id, data);
}
Conditional Types
// Basic conditional type
type IsString<T> = T extends string ? "yes" : "no";
// Extract array element type
type ArrayElement<T> = T extends Array<infer E> ? E : never;
type Items = ArrayElement<string[]>; // string
// Deep readonly
type DeepReadonly<T> = T extends object
? { readonly [P in keyof T]: DeepReadonly<T[P]> }
: T;
// Async return type
type AsyncReturn<T extends (...args: any) => Promise<any>> =
T extends (...args: any) => Promise<infer R> ? R : never;
The infer Keyword
// Extract function parameters
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
function greet(name: string, age: number): string { return name; }
type GreetParams = Parameters<typeof greet>; // [string, number]
type GreetReturn = ReturnType<typeof greet>; // string
// Unwrap Promise
type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;
type Unwrapped = Awaited<Promise<Promise<string>>>; // string
Mapped Types
// Custom mapped types
type Nullable<T> = { [P in keyof T]: T[P] | null };
// Re-map keys with 'as'
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
// Filter keys by value type
type KeysOfType<T, ValueType> = {
[K in keyof T]: T[K] extends ValueType ? K : never
}[keyof T];
interface Mixed { id: number; name: string; active: boolean; }
type StringKeys = KeysOfType<Mixed, string>; // "name"
Template Literal Types
// Event system with type safety
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
// CSS property builder
type Side = "top" | "right" | "bottom" | "left";
type Margin = `margin-${Side}`;
// Type-safe object paths
type PathKeys<T, Prefix extends string = ""> = {
[K in keyof T & string]: T[K] extends object
? PathKeys<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string];
interface Config { db: { host: string; port: number }; }
type ConfigPaths = PathKeys<Config>; // "db.host" | "db.port"
Discriminated Unions
type LoadingState<T> =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: Error };
function renderUser(state: LoadingState<User>) {
switch (state.status) {
case "success": return state.data.name; // TypeScript knows data exists
case "error": return state.error.message;
default: return "Loading...";
}
}
// Exhaustive check
function assertNever(x: never): never {
throw new Error(`Unexpected: ${x}`);
}
Summary
TypeScript's type system rewards investment:
- Generics eliminate duplication while maintaining type safety
- Conditional types transform types based on runtime shapes
inferextracts types from complex generics- Mapped types transform existing types systematically
- Template literals bring type safety to string patterns