Skip to content
16 / 19

Как Ρ‚ΠΈΠΏΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ-Ρ„Π°Π±Ρ€ΠΈΠΊΡƒ с generic?


ΠŸΠΎΠ»Π½Ρ‹ΠΉ ΠΎΡ‚Π²Π΅Ρ‚

Бамая простая Ρ„Π°Π±Ρ€ΠΈΠΊΠ° ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ класс ΠΈ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ Π΅Π³ΠΎ экзСмпляр:

TypeScript
// Базовая Ρ„Π°Π±Ρ€ΠΈΠΊΠ° β€” ΠΏΡ€ΠΈΠ½ΠΈΠΌΠ°Π΅Ρ‚ конструктор Π±Π΅Π· Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²

function create<T>(Ctor: new () => T): T {
  return new Ctor();
}

class User {
  name = "Alice";
}

class Product {
  title = "Laptop";
}

const user = create(User);       // Ρ‚ΠΈΠΏ: User
const product = create(Product); // Ρ‚ΠΈΠΏ: Product
console.log(user.name);          // "Alice"
console.log(product.title);      // "Laptop"

Когда конструктору Π½ΡƒΠΆΠ½Ρ‹ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Ρ‹, generic Ρ€Π°ΡΡˆΠΈΡ€ΡΠ΅Ρ‚ΡΡ:

TypeScript
// Π€Π°Π±Ρ€ΠΈΠΊΠ° с Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ конструктора

function createWith<T, A extends unknown[]>(
  Ctor: new (...args: A) => T,
  ...args: A
): T {
  return new Ctor(...args);
}

class Order {
  constructor(
    public id: number,
    public amount: number
  ) {}
}

const order = createWith(Order, 1, 99.99); // Ρ‚ΠΈΠΏ: Order
// createWith(Order, "abc");  // ошибка: string Π½Π΅ ΠΏΠΎΠ΄Ρ…ΠΎΠ΄ΠΈΡ‚ для number

Π€Π°Π±Ρ€ΠΈΠΊΠ° с ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠ΅ΠΉ вмСсто конструктора:

TypeScript
// Π€Π°Π±Ρ€ΠΈΠΊΠ° Ρ‡Π΅Ρ€Π΅Π· ΠΊΠΎΠ½Ρ„ΠΈΠ³-ΠΎΠ±ΡŠΠ΅ΠΊΡ‚
interface WidgetConfig<T> {
  type: string;
  build: (config: Record<string, unknown>) => T;
}

function createWidget<T>(widget: WidgetConfig<T>, config: Record<string, unknown>): T {
  return widget.build(config);
}

interface Chart {
  render(): void;
}

const chartFactory: WidgetConfig<Chart> = {
  type: "chart",
  build: (config) => ({
      render() {
        console.log("Rendering chart", config);
      },
    }),
};

const chart = createWidget(chartFactory, { data: [1, 2, 3] }); // Ρ‚ΠΈΠΏ: Chart
chart.render();

Π€Π°Π±Ρ€ΠΈΠΊΠ°-рССстр для Π½Π΅ΡΠΊΠΎΠ»ΡŒΠΊΠΈΡ… классов:

TypeScript
// РССстр Ρ„Π°Π±Ρ€ΠΈΠΊ с discriminated map
interface ShapeMap {
  circle: { radius: number };
  rect: { width: number; height: number };
}

function createShape<K extends keyof ShapeMap>(
  kind: K,
  params: ShapeMap[K]
): ShapeMap[K] & { kind: K } {
  return { ...params, kind };
}

const circle = createShape("circle", { radius: 10 }); // Ρ‚ΠΈΠΏ: { radius: number } & { kind: "circle" }

const rect = createShape("rect", { width: 5, height: 3 }); // Ρ‚ΠΈΠΏ: { width: number; height: number } & { kind: "rect" }

// createShape("circle", { width: 5 }); // ошибка: width Π½Π΅ сущСствуСт Π² { radius: number }

Π Π΅Π°Π»ΡŒΠ½Ρ‹Π΅ кСйсы

DI-ΠΊΠΎΠ½Ρ‚Π΅ΠΉΠ½Π΅Ρ€ с типобСзопасным resolve:

TypeScript
class Container {
  private registry = new Map<string, new (...args: never[]) => unknown>();

  register<T>(key: string, Ctor: new () => T): void {
    this.registry.set(key, Ctor as new (...args: never[]) => unknown);
  }

  resolve<T>(key: string): T {
    const Ctor = this.registry.get(key);
    if (!Ctor) throw new Error(`Not registered: ${key}`);
    return new Ctor() as T;
  }
}

Випичная ошибка β€” потСря Ρ‚ΠΈΠΏΠ° ΠΈΠ·-Π·Π° Function:

TypeScript
// ❌ НСт Ρ‚ΠΈΠΏΠΈΠ·Π°Ρ†ΠΈΠΈ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΠΎΠ³ΠΎ значСния

function badFactory(Ctor: Function) {
  return Ctor();
}

// βœ… Generic сохраняСт Ρ‚ΠΈΠΏ

function goodFactory<T>(Ctor: new () => T): T {
  return new Ctor();
}

РСзюмС

Π€Π°Π±Ρ€ΠΈΠΊΠ° с generic строится Π²ΠΎΠΊΡ€ΡƒΠ³ конструкторной сигнатуры new (...args: A) => T. Π­Ρ‚ΠΎ позволяСт TypeScript вывСсти Ρ‚ΠΈΠΏ Π²ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅ΠΌΠΎΠ³ΠΎ экзСмпляра автоматичСски. Для Ρ„Π°Π±Ρ€ΠΈΠΊ Π½Π° основС ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ mapped types ΠΈΠ»ΠΈ discriminated map, Ρ‡Ρ‚ΠΎΠ±Ρ‹ ΡΠ²ΡΠ·Π°Ρ‚ΡŒ ΠΊΠ»ΡŽΡ‡ с ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½Ρ‹ΠΌ Ρ‚ΠΈΠΏΠΎΠΌ Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Π°.

Как Ρ‚ΠΈΠΏΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ„ΡƒΠ½ΠΊΡ†ΠΈΡŽ-Ρ„Π°Π±Ρ€ΠΈΠΊΡƒ с generic? | JScriptiser