Why Advanced TypeScript?
TypeScript's type system is powerful enough to catch bugs at compile time that would otherwise make it to production. Mastering it leads to more maintainable code.
Generic Constraints
// Constrained generic
function getProperty(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name'); // type: string
const age = getProperty(user, 'age'); // type: number
// getProperty(user, 'invalid'); // Error!
// Generic with multiple constraints
interface HasId { id: string; }
interface HasTimestamp { createdAt: Date; }
function mergeEntities(
a: T,
b: Partial
): T {
return { ...a, ...b };
} Conditional Types
// Type that changes based on condition
type IsArray = T extends any[] ? true : false;
type A = IsArray; // true
type B = IsArray; // false
// Extract return type from promise
type UnwrapPromise = T extends Promise ? U : T;
type Result = UnwrapPromise>; // string
// Conditional type for API responses
type ApiResponse = T extends undefined
? { success: boolean }
: { success: boolean; data: T };
function handleResponse(data?: T): ApiResponse {
return data !== undefined
? { success: true, data }
: { success: true } as ApiResponse;
} Mapped Types
// Make all properties optional
type Partial = {
[K in keyof T]?: T[K];
};
// Make all properties required
type Required = {
[K in keyof T]-?: T[K];
};
// Make all properties readonly
type Readonly = {
readonly [K in keyof T]: T[K];
};
// Transform property types
type Getters = {
[K in keyof T as `get${Capitalize}`]: () => T[K];
};
interface User {
name: string;
age: number;
}
type UserGetters = Getters;
// { getName: () => string; getAge: () => number; } Template Literal Types
// Build string types programmatically
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize}`;
// 'onClick' | 'onFocus' | 'onBlur'
// CSS property types
type CSSProperty = 'margin' | 'padding';
type CSSDirection = 'top' | 'right' | 'bottom' | 'left';
type CSSPropertyDirection = `${CSSProperty}-${CSSDirection}`;
// 'margin-top' | 'margin-right' | ... | 'padding-left'
// API route types
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Route = '/users' | '/products';
type Endpoint = `${HttpMethod} ${Route}`;
// 'GET /users' | 'POST /users' | ... Type Guards
// User-defined type guard
interface Dog { bark(): void; breed: string; }
interface Cat { meow(): void; lives: number; }
function isDog(pet: Dog | Cat): pet is Dog {
return (pet as Dog).bark !== undefined;
}
function handlePet(pet: Dog | Cat) {
if (isDog(pet)) {
pet.bark(); // TypeScript knows it's a Dog
} else {
pet.meow(); // TypeScript knows it's a Cat
}
}
// Assertion function
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Not a string');
}
}
function process(input: unknown) {
assertIsString(input);
// input is now typed as string
console.log(input.toUpperCase());
}Discriminated Unions
// State machine types
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; error: string };
function renderState(state: State): string {
switch (state.status) {
case 'idle':
return 'Ready to fetch';
case 'loading':
return 'Loading...';
case 'success':
return `Found ${state.data.length} users`; // data is available
case 'error':
return `Error: ${state.error}`; // error is available
}
}
// Exhaustive checking
function assertNever(x: never): never {
throw new Error('Unexpected: ' + x);
}
function handle(state: State) {
switch (state.status) {
case 'idle': return;
case 'loading': return;
case 'success': return;
case 'error': return;
default: return assertNever(state); // Compile error if case missed
}
}Utility Types
// Pick specific properties
type UserPreview = Pick;
// Omit properties
type CreateUser = Omit;
// Record type for dictionaries
type UserCache = Record;
// Extract function parameters
type Params = Parameters;
// Extract return type
type Result = ReturnType;
// Extract awaited type
type Data = Awaited>; Branded Types
// Prevent mixing up IDs
type UserId = string & { readonly brand: unique symbol };
type OrderId = string & { readonly brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function getUser(id: UserId): User { ... }
function getOrder(id: OrderId): Order { ... }
const userId = createUserId('123');
getUser(userId); // OK
// getOrder(userId); // Error! Can't use UserId where OrderId expectedConclusion
Advanced TypeScript features help catch errors early and document your code's intent. Use them to build more reliable, maintainable applications.