TypeScript's type system is incredibly powerful and expressive. Let's dive deep into advanced type features that will make your code type-safe and maintainable.
Generics Deep Dive
Basic Generics
Generics allow you to create reusable components that work with a variety of types.
function identity<T>(arg: T): T {
return arg;
}
const num = identity<number>(42);
const str = identity("hello");Generic Constraints
Constrain generics to specific shapes:
interface Lengthwise {
length: number;
}
function logLength<T extends Lengthwise>(arg: T): void {
console.log(arg.length);
}
logLength("hello"); // Works
logLength([1, 2, 3]); // Works
logLength(42); // ErrorMultiple Type Parameters
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
const p = pair(1, "hello");Conditional Types
Conditional types allow you to choose types based on conditions.
type NonNullable<T> = T extends null | undefined ? never : T;
type Result = NonNullable<string | null>; // stringinfer Keyword
Extract type from another type:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type AsyncData = Promise<string>;
type Data = UnwrapPromise<AsyncData>; // stringMapped Types
Transform properties of an existing type:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface User {
id: number;
name: string;
email: string;
}
type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;Utility Types
TypeScript Built-in Utilities
// Pick - Select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;
// Omit - Remove specific properties
type CreateUser = Omit<User, 'id'>;
// Record - Create object type
type UserMap = Record<string, User>;
// Exclude - Remove types from union
type Primitives = Exclude<string | number | boolean, string>;
// Extract - Extract types from union
type Strings = Extract<string | number | boolean, string>;
// ReturnType - Get return type of function
type FnReturn = ReturnType<() => string>;
// Parameters - Get parameter types of function
type FnParams = Parameters<(a: string, b: number) => void>;Template Literal Types
Create string types from patterns:
type EventName<T extends string> = `${T}Changed`;
type ClickEvent = EventName<"click">; // "clickChanged"
type CssProperty = `margin${"top" | "bottom" | "left" | "right"}`;
// "marginTop" | "marginBottom" | "marginLeft" | "marginRight"Branded Types
Add brand to primitive types for type safety:
type USD = number & { readonly __brand: unique symbol };
type EUR = number & { readonly __brand: unique symbol };
const usd = (amount: number): USD = amount as USD;
const eur = (amount: number): EUR = amount as EUR;
const addFunds = (wallet: USD, amount: USD): USD => {
return usd(wallet + amount);
};
let wallet = usd(100);
wallet = addFunds(wallet, usd(50));
// wallet = addFunds(wallet, eur(50)); // ErrorType Guards and Discriminators
typeof Type Guard
function process(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value * 2;
}instanceof Type Guard
class Dog {
bark() { console.log("Woof!"); }
}
class Cat {
meow() { console.log("Meow!"); }
}
function makeSound(animal: Dog | Cat) {
if (animal instanceof Dog) {
animal.bark();
} else {
animal.meow();
}
}Discriminated Unions
interface Success<T> {
type: "success";
data: T;
}
interface Error {
type: "error";
message: string;
}
type Result<T> = Success<T> | Error;
function handleResult<T>(result: Result<T>) {
if (result.type === "success") {
console.log(result.data);
} else {
console.log(result.message);
}
}Advanced Pattern Matching
type Match = {
pattern: RegExp;
handler: (match: RegExpMatchArray) => void;
};
function route(path: string, routes: Match[]) {
for (const route of routes) {
const match = path.match(route.pattern);
if (match) {
route.handler(match);
return;
}
}
}Conclusion
TypeScript's advanced type system enables you to catch errors at compile time, improve IDE support, and make your code more maintainable. Master generics, conditional types, and utility types to write truly type-safe code.