スキル一覧に戻る
djankies

using-generics

by djankies

0🍴 0📅 2025年11月25日
GitHubで見るManusで実行

SKILL.md


name: using-generics description: Teaches generic constraints, avoiding any in generic defaults, and mapped types in TypeScript. Use when creating reusable functions, components, or types that work with multiple types while maintaining type safety. allowed-tools: Read, Write, Edit, Glob, Grep, Bash, Task, TodoWrite version: 1.0.0

  • 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.

スコア

総合スコア

60/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です