Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Part of a 72-plugin marketplace with 112 AI agents and 146 skills for Claude Code development automation.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: typescript-advanced-types3description: Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects.4---56# TypeScript Advanced Types78Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications.910## When to Use This Skill1112- Building type-safe libraries or frameworks13- Creating reusable generic components14- Implementing complex type inference logic15- Designing type-safe API clients16- Building form validation systems17- Creating strongly-typed configuration objects18- Implementing type-safe state management19- Migrating JavaScript codebases to TypeScript2021## Core Concepts2223### 1. Generics2425**Purpose:** Create reusable, type-flexible components while maintaining type safety.2627**Basic Generic Function:**2829```typescript30function identity<T>(value: T): T {31return value;32}3334const num = identity<number>(42); // Type: number35const str = identity<string>("hello"); // Type: string36const auto = identity(true); // Type inferred: boolean37```3839**Generic Constraints:**4041```typescript42interface HasLength {43length: number;44}4546function logLength<T extends HasLength>(item: T): T {47console.log(item.length);48return item;49}5051logLength("hello"); // OK: string has length52logLength([1, 2, 3]); // OK: array has length53logLength({ length: 10 }); // OK: object has length54// logLength(42); // Error: number has no length55```5657**Multiple Type Parameters:**5859```typescript60function merge<T, U>(obj1: T, obj2: U): T & U {61return { ...obj1, ...obj2 };62}6364const merged = merge({ name: "John" }, { age: 30 });65// Type: { name: string } & { age: number }66```6768### 2. Conditional Types6970**Purpose:** Create types that depend on conditions, enabling sophisticated type logic.7172**Basic Conditional Type:**7374```typescript75type IsString<T> = T extends string ? true : false;7677type A = IsString<string>; // true78type B = IsString<number>; // false79```8081**Extracting Return Types:**8283```typescript84type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;8586function getUser() {87return { id: 1, name: "John" };88}8990type User = ReturnType<typeof getUser>;91// Type: { id: number; name: string; }92```9394**Distributive Conditional Types:**9596```typescript97type ToArray<T> = T extends any ? T[] : never;9899type StrOrNumArray = ToArray<string | number>;100// Type: string[] | number[]101```102103**Nested Conditions:**104105```typescript106type TypeName<T> = T extends string107? "string"108: T extends number109? "number"110: T extends boolean111? "boolean"112: T extends undefined113? "undefined"114: T extends Function115? "function"116: "object";117118type T1 = TypeName<string>; // "string"119type T2 = TypeName<() => void>; // "function"120```121122### 3. Mapped Types123124**Purpose:** Transform existing types by iterating over their properties.125126**Basic Mapped Type:**127128```typescript129type Readonly<T> = {130readonly [P in keyof T]: T[P];131};132133interface User {134id: number;135name: string;136}137138type ReadonlyUser = Readonly<User>;139// Type: { readonly id: number; readonly name: string; }140```141142**Optional Properties:**143144```typescript145type Partial<T> = {146[P in keyof T]?: T[P];147};148149type PartialUser = Partial<User>;150// Type: { id?: number; name?: string; }151```152153**Key Remapping:**154155```typescript156type Getters<T> = {157[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];158};159160interface Person {161name: string;162age: number;163}164165type PersonGetters = Getters<Person>;166// Type: { getName: () => string; getAge: () => number; }167```168169**Filtering Properties:**170171```typescript172type PickByType<T, U> = {173[K in keyof T as T[K] extends U ? K : never]: T[K];174};175176interface Mixed {177id: number;178name: string;179age: number;180active: boolean;181}182183type OnlyNumbers = PickByType<Mixed, number>;184// Type: { id: number; age: number; }185```186187### 4. Template Literal Types188189**Purpose:** Create string-based types with pattern matching and transformation.190191**Basic Template Literal:**192193```typescript194type EventName = "click" | "focus" | "blur";195type EventHandler = `on${Capitalize<EventName>}`;196// Type: "onClick" | "onFocus" | "onBlur"197```198199**String Manipulation:**200201```typescript202type UppercaseGreeting = Uppercase<"hello">; // "HELLO"203type LowercaseGreeting = Lowercase<"HELLO">; // "hello"204type CapitalizedName = Capitalize<"john">; // "John"205type UncapitalizedName = Uncapitalize<"John">; // "john"206```207208**Path Building:**209210```typescript211type Path<T> = T extends object212? {213[K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never;214}[keyof T]215: never;216217interface Config {218server: {219host: string;220port: number;221};222database: {223url: string;224};225}226227type ConfigPath = Path<Config>;228// Type: "server" | "database" | "server.host" | "server.port" | "database.url"229```230231### 5. Utility Types232233**Built-in Utility Types:**234235```typescript236// Partial<T> - Make all properties optional237type PartialUser = Partial<User>;238239// Required<T> - Make all properties required240type RequiredUser = Required<PartialUser>;241242// Readonly<T> - Make all properties readonly243type ReadonlyUser = Readonly<User>;244245// Pick<T, K> - Select specific properties246type UserName = Pick<User, "name" | "email">;247248// Omit<T, K> - Remove specific properties249type UserWithoutPassword = Omit<User, "password">;250251// Exclude<T, U> - Exclude types from union252type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"253254// Extract<T, U> - Extract types from union255type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"256257// NonNullable<T> - Exclude null and undefined258type T3 = NonNullable<string | null | undefined>; // string259260// Record<K, T> - Create object type with keys K and values T261type PageInfo = Record<"home" | "about", { title: string }>;262```263264## Advanced Patterns265266### Pattern 1: Type-Safe Event Emitter267268```typescript269type EventMap = {270"user:created": { id: string; name: string };271"user:updated": { id: string };272"user:deleted": { id: string };273};274275class TypedEventEmitter<T extends Record<string, any>> {276private listeners: {277[K in keyof T]?: Array<(data: T[K]) => void>;278} = {};279280on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {281if (!this.listeners[event]) {282this.listeners[event] = [];283}284this.listeners[event]!.push(callback);285}286287emit<K extends keyof T>(event: K, data: T[K]): void {288const callbacks = this.listeners[event];289if (callbacks) {290callbacks.forEach((callback) => callback(data));291}292}293}294295const emitter = new TypedEventEmitter<EventMap>();296297emitter.on("user:created", (data) => {298console.log(data.id, data.name); // Type-safe!299});300301emitter.emit("user:created", { id: "1", name: "John" });302// emitter.emit("user:created", { id: "1" }); // Error: missing 'name'303```304305### Pattern 2: Type-Safe API Client306307```typescript308type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";309310type EndpointConfig = {311"/users": {312GET: { response: User[] };313POST: { body: { name: string; email: string }; response: User };314};315"/users/:id": {316GET: { params: { id: string }; response: User };317PUT: { params: { id: string }; body: Partial<User>; response: User };318DELETE: { params: { id: string }; response: void };319};320};321322type ExtractParams<T> = T extends { params: infer P } ? P : never;323type ExtractBody<T> = T extends { body: infer B } ? B : never;324type ExtractResponse<T> = T extends { response: infer R } ? R : never;325326class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {327async request<Path extends keyof Config, Method extends keyof Config[Path]>(328path: Path,329method: Method,330...[options]: ExtractParams<Config[Path][Method]> extends never331? ExtractBody<Config[Path][Method]> extends never332? []333: [{ body: ExtractBody<Config[Path][Method]> }]334: [335{336params: ExtractParams<Config[Path][Method]>;337body?: ExtractBody<Config[Path][Method]>;338},339]340): Promise<ExtractResponse<Config[Path][Method]>> {341// Implementation here342return {} as any;343}344}345346const api = new APIClient<EndpointConfig>();347348// Type-safe API calls349const users = await api.request("/users", "GET");350// Type: User[]351352const newUser = await api.request("/users", "POST", {353body: { name: "John", email: "[email protected]" },354});355// Type: User356357const user = await api.request("/users/:id", "GET", {358params: { id: "123" },359});360// Type: User361```362363### Pattern 3: Builder Pattern with Type Safety364365```typescript366type BuilderState<T> = {367[K in keyof T]: T[K] | undefined;368};369370type RequiredKeys<T> = {371[K in keyof T]-?: {} extends Pick<T, K> ? never : K;372}[keyof T];373374type OptionalKeys<T> = {375[K in keyof T]-?: {} extends Pick<T, K> ? K : never;376}[keyof T];377378type IsComplete<T, S> =379RequiredKeys<T> extends keyof S380? S[RequiredKeys<T>] extends undefined381? false382: true383: false;384385class Builder<T, S extends BuilderState<T> = {}> {386private state: S = {} as S;387388set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {389this.state[key] = value;390return this as any;391}392393build(this: IsComplete<T, S> extends true ? this : never): T {394return this.state as T;395}396}397398interface User {399id: string;400name: string;401email: string;402age?: number;403}404405const builder = new Builder<User>();406407const user = builder408.set("id", "1")409.set("name", "John")410.set("email", "[email protected]")411.build(); // OK: all required fields set412413// const incomplete = builder414// .set("id", "1")415// .build(); // Error: missing required fields416```417418### Pattern 4: Deep Readonly/Partial419420```typescript421type DeepReadonly<T> = {422readonly [P in keyof T]: T[P] extends object423? T[P] extends Function424? T[P]425: DeepReadonly<T[P]>426: T[P];427};428429type DeepPartial<T> = {430[P in keyof T]?: T[P] extends object431? T[P] extends Array<infer U>432? Array<DeepPartial<U>>433: DeepPartial<T[P]>434: T[P];435};436437interface Config {438server: {439host: string;440port: number;441ssl: {442enabled: boolean;443cert: string;444};445};446database: {447url: string;448pool: {449min: number;450max: number;451};452};453}454455type ReadonlyConfig = DeepReadonly<Config>;456// All nested properties are readonly457458type PartialConfig = DeepPartial<Config>;459// All nested properties are optional460```461462### Pattern 5: Type-Safe Form Validation463464```typescript465type ValidationRule<T> = {466validate: (value: T) => boolean;467message: string;468};469470type FieldValidation<T> = {471[K in keyof T]?: ValidationRule<T[K]>[];472};473474type ValidationErrors<T> = {475[K in keyof T]?: string[];476};477478class FormValidator<T extends Record<string, any>> {479constructor(private rules: FieldValidation<T>) {}480481validate(data: T): ValidationErrors<T> | null {482const errors: ValidationErrors<T> = {};483let hasErrors = false;484485for (const key in this.rules) {486const fieldRules = this.rules[key];487const value = data[key];488489if (fieldRules) {490const fieldErrors: string[] = [];491492for (const rule of fieldRules) {493if (!rule.validate(value)) {494fieldErrors.push(rule.message);495}496}497498if (fieldErrors.length > 0) {499errors[key] = fieldErrors;500hasErrors = true;501}502}503}504505return hasErrors ? errors : null;506}507}508509interface LoginForm {510email: string;511password: string;512}513514const validator = new FormValidator<LoginForm>({515email: [516{517validate: (v) => v.includes("@"),518message: "Email must contain @",519},520{521validate: (v) => v.length > 0,522message: "Email is required",523},524],525password: [526{527validate: (v) => v.length >= 8,528message: "Password must be at least 8 characters",529},530],531});532533const errors = validator.validate({534email: "invalid",535password: "short",536});537// Type: { email?: string[]; password?: string[]; } | null538```539540### Pattern 6: Discriminated Unions541542```typescript543type Success<T> = {544status: "success";545data: T;546};547548type Error = {549status: "error";550error: string;551};552553type Loading = {554status: "loading";555};556557type AsyncState<T> = Success<T> | Error | Loading;558559function handleState<T>(state: AsyncState<T>): void {560switch (state.status) {561case "success":562console.log(state.data); // Type: T563break;564case "error":565console.log(state.error); // Type: string566break;567case "loading":568console.log("Loading...");569break;570}571}572573// Type-safe state machine574type State =575| { type: "idle" }576| { type: "fetching"; requestId: string }577| { type: "success"; data: any }578| { type: "error"; error: Error };579580type Event =581| { type: "FETCH"; requestId: string }582| { type: "SUCCESS"; data: any }583| { type: "ERROR"; error: Error }584| { type: "RESET" };585586function reducer(state: State, event: Event): State {587switch (state.type) {588case "idle":589return event.type === "FETCH"590? { type: "fetching", requestId: event.requestId }591: state;592case "fetching":593if (event.type === "SUCCESS") {594return { type: "success", data: event.data };595}596if (event.type === "ERROR") {597return { type: "error", error: event.error };598}599return state;600case "success":601case "error":602return event.type === "RESET" ? { type: "idle" } : state;603}604}605```606607## Type Inference Techniques608609### 1. Infer Keyword610611```typescript612// Extract array element type613type ElementType<T> = T extends (infer U)[] ? U : never;614615type NumArray = number[];616type Num = ElementType<NumArray>; // number617618// Extract promise type619type PromiseType<T> = T extends Promise<infer U> ? U : never;620621type AsyncNum = PromiseType<Promise<number>>; // number622623// Extract function parameters624type Parameters<T> = T extends (...args: infer P) => any ? P : never;625626function foo(a: string, b: number) {}627type FooParams = Parameters<typeof foo>; // [string, number]628```629630### 2. Type Guards631632```typescript633function isString(value: unknown): value is string {634return typeof value === "string";635}636637function isArrayOf<T>(638value: unknown,639guard: (item: unknown) => item is T,640): value is T[] {641return Array.isArray(value) && value.every(guard);642}643644const data: unknown = ["a", "b", "c"];645646if (isArrayOf(data, isString)) {647data.forEach((s) => s.toUpperCase()); // Type: string[]648}649```650651### 3. Assertion Functions652653```typescript654function assertIsString(value: unknown): asserts value is string {655if (typeof value !== "string") {656throw new Error("Not a string");657}658}659660function processValue(value: unknown) {661assertIsString(value);662// value is now typed as string663console.log(value.toUpperCase());664}665```666667## Best Practices6686691. **Use `unknown` over `any`**: Enforce type checking6702. **Prefer `interface` for object shapes**: Better error messages6713. **Use `type` for unions and complex types**: More flexible6724. **Leverage type inference**: Let TypeScript infer when possible6735. **Create helper types**: Build reusable type utilities6746. **Use const assertions**: Preserve literal types6757. **Avoid type assertions**: Use type guards instead6768. **Document complex types**: Add JSDoc comments6779. **Use strict mode**: Enable all strict compiler options67810. **Test your types**: Use type tests to verify type behavior679680## Type Testing681682```typescript683// Type assertion tests684type AssertEqual<T, U> = [T] extends [U]685? [U] extends [T]686? true687: false688: false;689690type Test1 = AssertEqual<string, string>; // true691type Test2 = AssertEqual<string, number>; // false692type Test3 = AssertEqual<string | number, string>; // false693694// Expect error helper695type ExpectError<T extends never> = T;696697// Example usage698type ShouldError = ExpectError<AssertEqual<string, number>>;699```700701## Common Pitfalls7027031. **Over-using `any`**: Defeats the purpose of TypeScript7042. **Ignoring strict null checks**: Can lead to runtime errors7053. **Too complex types**: Can slow down compilation7064. **Not using discriminated unions**: Misses type narrowing opportunities7075. **Forgetting readonly modifiers**: Allows unintended mutations7086. **Circular type references**: Can cause compiler errors7097. **Not handling edge cases**: Like empty arrays or null values710711## Performance Considerations712713- Avoid deeply nested conditional types714- Use simple types when possible715- Cache complex type computations716- Limit recursion depth in recursive types717- Use build tools to skip type checking in production718