JS Tower
JavaScript & TypeScriptМодуль 8: Продвинутый TypeScript

Generics

Обобщённые типы в TypeScript

Цель урока

В этом уроке ты научишься:

  • Создавать generic функции
  • Использовать constraints
  • Создавать generic интерфейсы и классы

Зачем нужны generics

// Без generics — теряем информацию о типе
function identity(value: any): any {
  return value;
}

let result = identity("hello"); // тип: any

// С generics — сохраняем тип
function identity<T>(value: T): T {
  return value;
}

let result = identity<string>("hello"); // тип: string
let result2 = identity(42);             // тип: number (вывод)

Generic функции

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

let num = first([1, 2, 3]);      // number | undefined
let str = first(["a", "b"]);     // string | undefined

// Несколько параметров
function pair<T, U>(a: T, b: U): [T, U] {
  return [a, b];
}

let p = pair("hello", 42); // [string, number]

Constraints

Ограничения на generic тип:

// T должен иметь свойство length
function logLength<T extends { length: number }>(value: T): void {
  console.log(value.length);
}

logLength("hello");     // 5
logLength([1, 2, 3]);   // 3
// logLength(123);      // Ошибка!

// Ключи объекта
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let user = { name: "Иван", age: 25 };
let name = getProperty(user, "name"); // string
// getProperty(user, "email");        // Ошибка!

Generic интерфейсы

interface Box<T> {
  value: T;
}

let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };

// С методами
interface Collection<T> {
  items: T[];
  add(item: T): void;
  get(index: number): T;
}

Generic классы

class Stack<T> {
  private items: T[] = [];
  
  push(item: T): void {
    this.items.push(item);
  }
  
  pop(): T | undefined {
    return this.items.pop();
  }
  
  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }
}

let numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2

Значения по умолчанию

interface Response<T = any> {
  data: T;
  status: number;
}

let response1: Response = { data: "anything", status: 200 };
let response2: Response<{ name: string }> = {
  data: { name: "Иван" },
  status: 200
};

Практические примеры

API Response

interface ApiResponse<T> {
  data: T;
  status: number;
  message: string;
}

async function fetchApi<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);
  return response.json();
}

interface User {
  id: number;
  name: string;
}

let users = await fetchApi<User[]>("/api/users");
// users.data имеет тип User[]

Практика

Задание 1: Generic функция

Задача: Создай функцию last, возвращающую последний элемент.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
3
c

Задание 2: Generic интерфейс

Задача: Создай интерфейс для результата.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
Иван

Задание 3: Constraints

Задача: Создай функцию с constraint.

Запустите код для проверки
Loading...
Ваш вывод:
Ожидаемый результат:
{"a":1,"b":2}

Проверь себя

  1. Зачем нужны generics?
  2. Что делает extends в generic?
  3. Как задать значение по умолчанию?