2024-08-31
Master advanced TypeScript patterns including generics, conditional types, mapped types, and type guards.
TypeScript's powerful type system enables building robust, maintainable applications at scale. This guide explores advanced patterns including generics, conditional types, utility types, and type-safe design patterns.
// Generic function with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// Generic interface with default type
interface ApiResponse<T = unknown> {
data: T;
status: number;
message: string;
}
// Generic class with multiple type parameters
class DataStore<K extends string | number, V> {
private store = new Map<K, V>();
set(key: K, value: V): void {
this.store.set(key, value);
}
get(key: K): V | undefined {
return this.store.get(key);
}
}
// Conditional generic constraints
type IsArray<T> = T extends any[] ? true : false;
type ArrayElement<T> = T extends (infer U)[] ? U : never;
// Conditional type for extracting promise value
type Awaited<T> = T extends Promise<infer U> ? U : T;
// Distributive conditional types
type NonNullable<T> = T extends null | undefined ? never : T;
// Function return type extraction
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// Complex conditional logic
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
// Make all properties optional
type Partial<T> = {
[P in keyof T]?: T[P];
};
// Make all properties readonly
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// Create type with specific keys
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// Custom mapped type with template literals
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
interface Person {
name: string;
age: number;
}
type PersonGetters = Getters<Person>;
// Result: { getName: () => string; getAge: () => number; }
// User-defined type guards
interface Bird { fly(): void; layEggs(): void; }
interface Fish { swim(): void; layEggs(): void; }
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// Type predicates with generics
function isNotNull<T>(value: T | null): value is T {
return value !== null;
}
// Discriminated unions
type Result<T, E> =
| { success: true; data: T }
| { success: false; error: E };
function processResult<T, E>(result: Result<T, E>) {
if (result.success) {
console.log(result.data); // T
} else {
console.error(result.error); // E
}
}
// Assertion functions
function assertIsDefined<T>(value: T | undefined): asserts value is T {
if (value === undefined) {
throw new Error('Value is undefined');
}
}
// Deep partial type
type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
} : T;
// Deep readonly type
type DeepReadonly<T> = T extends primitive ? T : {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};
// Union to intersection
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
((k: infer I) => void) ? I : never;
// Flatten object type
type Flatten<T> = T extends object ? {
[K in keyof T]: T[K];
} : T;
class QueryBuilder<T = {}> {
private query: T;
constructor(query: T = {} as T) {
this.query = query;
}
select<K extends string>(fields: K[]): QueryBuilder<T & { select: K[] }> {
return new QueryBuilder({ ...this.query, select: fields });
}
where<K extends string, V>(field: K, value: V): QueryBuilder<T & { where: { [key in K]: V } }> {
return new QueryBuilder({ ...this.query, where: { [field]: value } as any });
}
build(): T {
return this.query;
}
}
// Type-safe builder usage
const query = new QueryBuilder()
.select(['id', 'name'])
.where('status', 'active')
.build();
// Type: { select: string[]; where: { status: string } }
// Event handler types
type EventHandler<T extends string> = `on${Capitalize<T>}`;
type ClickHandler = EventHandler<'click'>; // "onClick"
// CSS unit types
type CSSUnit = 'px' | 'em' | 'rem' | '%';
type CSSValue = `${number}${CSSUnit}`;
// Route parameter extraction
type ExtractRouteParams<T extends string> =
T extends `${infer Start}/:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
: T extends `${infer Start}/:${infer Param}`
? { [K in Param]: string }
: {};
type UserRoute = ExtractRouteParams<'/users/:id/posts/:postId'>;
// Result: { id: string; postId: string }
Use Unknown Over Any
Prefer 'unknown' type over 'any' for type safety. Unknown requires type checking before use.
Strict Configuration
Enable strict mode in tsconfig.json for maximum type safety benefits.
Type Inference
Let TypeScript infer types when possible. Explicit types are only needed when inference isn't sufficient.
Published on 2024-08-31 • Category: Programming
← Back to BlogFree online developer tools and utilities for encoding, formatting, generating, and analyzing data. No registration required - all tools work directly in your browser.
Built for developers, by developers. Privacy-focused and open source.
Free online tools for Base64 encoding, JSON formatting, URL encoding, hash generation, UUID creation, QR codes, JWT decoding, timestamp conversion, regex testing, and more.
© 2024 NarvikHub. All rights reserved.