using-generics

📁 djankies/claude-configs 📅 9 days ago
1
总安装量
1
周安装量
#54908
全站排名
安装命令
npx skills add https://github.com/djankies/claude-configs --skill using-generics

Agent 安装分布

replit 1
junie 1
windsurf 1
trae 1
qoder 1
opencode 1

Skill 文档

  • Creating reusable functions or classes
  • Designing generic APIs or libraries
  • Working with generic defaults (<T = ...>)
  • Implementing mapped types or conditional types
  • User mentions generics, type parameters, constraints, or reusable types

Key Concepts:

  1. Generic Parameters: <T> – Type variables that get filled in at call site
  2. Constraints: <T extends Shape> – Limits what types T can be
  3. Defaults: <T = string> – Fallback when type not provided
  4. Mapped Types: Transform existing types systematically

Impact: Write flexible, reusable code without sacrificing type safety.

Step 1: Identify the Varying Type

What changes between uses?

  • Data type in container (Array, Promise)
  • Object shape variations
  • Return type based on input
  • Multiple related types

Step 2: Choose Constraint Strategy

No Constraint – Accepts any type

function identity<T>(value: T): T {
  return value;
}

Extends Constraint – Requires specific shape

function logId<T extends { id: string }>(item: T): void {
  console.log(item.id);
}

Union Constraint – Limited set of types

function process<T extends string | number>(value: T): T {
  return value;
}

Multiple Constraints – Multiple type parameters with relationships

function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

Step 3: Set Default (If Needed)

Prefer no default over any default:

interface ApiResponse<T = unknown> { data: T; }

Or require explicit type parameter:

interface ApiResponse<T> { data: T; }

❌ No constraint (too permissive)

function getProperty<T>(obj: T, key: string): any {
  return obj[key];
}

Problems:

  • obj[key] not type-safe (T might not have string keys)
  • Returns any (loses type information)
  • No IDE autocomplete for key

✅ Proper constraints

function getProperty<T extends object, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 30 };
const name = getProperty(user, "name");
const invalid = getProperty(user, "invalid");

Benefits:

  • Type-safe key access
  • Return type is T[K] (actual property type)
  • IDE autocompletes valid keys
  • Compile error for invalid keys

Example 2: Generic Defaults

❌ Using any default (unsafe)

interface Result<T = any> {
  data: T;
  error?: string;
}

const result: Result = { data: "anything" };
result.data.nonExistentProperty;

✅ Using unknown default (safe)

interface Result<T = unknown> {
  data: T;
  error?: string;
}

const result: Result = { data: "anything" };

if (typeof result.data === "string") {
  console.log(result.data.toUpperCase());
}

✅ No default (best)

interface Result<T> {
  data: T;
  error?: string;
}

const result: Result<string> = { data: "specific type" };
console.log(result.data.toUpperCase());

Example 3: Constraining Generic Parameters

Example: Ensuring object has id

interface HasId {
  id: string;
}

function findById<T extends HasId>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

const users = [
  { id: "1", name: "Alice" },
  { id: "2", name: "Bob" }
];

const user = findById(users, "1");

Example: Ensuring constructable type

interface Constructable<T> {
  new (...args: any[]): T;
}

function create<T>(Constructor: Constructable<T>): T {
  return new Constructor();
}

class User {
  name = "Anonymous";
}

const user = create(User);

Example: Ensuring array element type

function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const first = firstElement([1, 2, 3]);
const second = firstElement(["a", "b"]);

Example 4: Multiple Type Parameters

Example: Key-value mapping

function mapObject<T extends object, U>(
  obj: T,
  fn: (value: T[keyof T]) => U
): Record<keyof T, U> {
  const result = {} as Record<keyof T, U>;

  for (const key in obj) {
    result[key] = fn(obj[key]);
  }

  return result;
}

const user = { name: "Alice", age: 30 };
const lengths = mapObject(user, val => String(val).length);

Example: Conditional return types

function parse<T extends "json" | "text">(
  response: Response,
  type: T
): T extends "json" ? Promise<unknown> : Promise<string> {
  if (type === "json") {
    return response.json() as any;
  }
  return response.text() as any;
}

const json = await parse(response, "json");
const text = await parse(response, "text");

Example 5: Mapped Types

Making properties optional:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

const partialUser: Partial<User> = { name: "Alice" };

Making properties readonly:

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

Picking specific properties:

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

type UserPreview = Pick<User, "id" | "name">;

See references/detailed-examples.md for DeepPartial, FilterByType, and other complex mapped type patterns.


Example 6: Conditional Types

Unwrap promise type:

type Awaited<T> = T extends Promise<infer U> ? U : T;

Extract function parameters:

type Parameters<T> = T extends (...args: infer P) => any ? P : never;

See references/detailed-examples.md for more conditional type patterns including FilterByType, nested promise unwrapping, and parameter extraction.

In this skill:

  • references/detailed-examples.md – DeepPartial, FilterByType, conditional types, constructables
  • references/common-patterns.md – Array ops, object utils, Promise utils, builders
  • references/advanced-patterns.md – Recursive generics, variadic tuples, branded types, HKTs

Related skills:

  • Use the using-type-guards skill for narrowing generic types
  • Use the avoiding-any-types skill for generic defaults
  • Use the using-runtime-checks skill for validating generic data
  • Use extends to constrain generic parameters when accessing properties
  • Use keyof T for type-safe property access
  • Use unknown for generic defaults if truly dynamic
  • Specify return type based on generic parameters

SHOULD:

  • Prefer no default over any default
  • Use descriptive type parameter names for complex generics
  • Infer type parameters from usage when possible
  • Use helper types (Pick, Omit, Partial) over manual mapping

NEVER:

  • Use any as generic default
  • Access properties on unconstrained generics
  • Use as any to bypass generic constraints
  • Create overly complex nested generics (split into smaller types)

Array Operations

function last<T>(arr: T[]): T | undefined {
  return arr[arr.length - 1];
}

function chunk<T>(arr: T[], size: number): T[][] {
  const chunks: T[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    chunks.push(arr.slice(i, i + size));
  }
  return chunks;
}

Object Utilities

function pick<T extends object, K extends keyof T>(
  obj: T,
  ...keys: K[]
): Pick<T, K> {
  const result = {} as Pick<T, K>;
  for (const key of keys) {
    result[key] = obj[key];
  }
  return result;
}

Class Generics

class Container<T> {
  constructor(private value: T) {}

  map<U>(fn: (value: T) => U): Container<U> {
    return new Container(fn(this.value));
  }
}

See references/common-patterns.md for complete implementations including Promise utilities, builders, event emitters, and more.

  1. Constraints:

    • Generic parameters constrained when accessing properties
    • keyof used for property key types
    • extends used appropriately
  2. Defaults:

    • No any defaults
    • unknown used for truly dynamic defaults
    • Or no default (require explicit type)
  3. Type Inference:

    • Type parameters inferred from usage
    • Explicit types only when inference fails
    • Return types correctly derived from generics
  4. Complexity:

    • Generic types are understandable
    • Complex types split into smaller pieces
    • Helper types used appropriately

For advanced patterns including:

  • Recursive Generics (DeepReadonly, DeepPartial)
  • Variadic Tuple Types (type-safe array concatenation)
  • Template Literal Types (string manipulation at type level)
  • Branded Types (nominal typing in structural system)
  • Distributive Conditional Types
  • Higher-Kinded Types (simulation)

See references/advanced-patterns.md for detailed implementations and examples.