Generics в TypeScript: полное руководство по использованию

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в Generics TypeScript

TypeScript — это строго типизированный язык программирования, который добавляет статическую типизацию в JavaScript. Одной из самых мощных и часто недооцененных возможностей TypeScript являются Generics (дженерики, обобщенные типы). Если вы когда-либо писали функцию, которая должна работать с разными типами данных, или создавали компонент, который должен быть универсальным, то Generics — это именно то, что вам нужно.

В этой статье мы подробно разберем, что такое дженерики в TypeScript, зачем они нужны и как их использовать на практике. Вы узнаете, как сделать ваш код более гибким, безопасным и переиспользуемым.

Что такое Generics и зачем они нужны?

Generics (обобщенные типы) — это механизм, который позволяет создавать компоненты (функции, классы, интерфейсы), работающие с различными типами данных, не теряя при этом информацию о типе. Представьте, что вы пишете функцию, которая возвращает первый элемент массива. Без дженериков вам пришлось бы использовать тип any, что лишает вас преимуществ строгой типизации.

Зачем использовать Generics?

  • Безопасность типов: TypeScript проверяет типы на этапе компиляции, предотвращая ошибки.
  • Переиспользование кода: Одна функция или класс могут работать с любыми типами данных.
  • Улучшение читаемости: Код становится более понятным и самодокументируемым.
  • Сокращение дублирования: Вам не нужно писать отдельные функции для каждого типа данных.

Без Generics вы бы написали что-то вроде этого:

// Плохо: теряем информацию о типе
function getFirstElement(arr: any[]): any {
  return arr[0];
}

const num = getFirstElement([1, 2, 3]); // Тип any
console.log(num.toUpperCase()); // Ошибка во время выполнения!

С Generics код становится безопасным и гибким:

// Хорошо: сохраняем тип
function getFirstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

const num = getFirstElement([1, 2, 3]); // Тип number
const str = getFirstElement(['a', 'b', 'c']); // Тип string

// num.toUpperCase(); // Ошибка компиляции! number не имеет метода toUpperCase

Основы синтаксиса Generics

Синтаксис с угловыми скобками

Generics используют угловые скобки <> для указания параметров типа. Обычно параметры обозначаются одной буквой: T (Type), U, V и т.д. Однако вы можете использовать любые осмысленные имена.

// Простейший пример: функция с одним параметром типа
function identity<T>(arg: T): T {
  return arg;
}

// Явное указание типа
let result1 = identity<string>('Hello');

// Вывод типа (TypeScript сам определяет тип)
let result2 = identity(42); // Тип number

Несколько параметров типа

Вы можете использовать несколько параметров типа в одной функции или классе:

function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const merged = merge({ name: 'John' }, { age: 30 });
// Тип: { name: string } & { age: number }
console.log(merged.name); // John
console.log(merged.age);  // 30

Ограничения (Constraints)

Иногда нужно ограничить типы, которые могут быть переданы в дженерик. Для этого используется ключевое слово extends:

interface HasLength {
  length: number;
}

// Функция работает только с типами, у которых есть свойство length
function logLength<T extends HasLength>(arg: T): T {
  console.log(arg.length);
  return arg;
}

logLength('Hello'); // Работает: 5
logLength([1, 2, 3]); // Работает: 3
// logLength(123); // Ошибка! number не имеет length

Применение Generics в функциях, интерфейсах и классах

Generics в функциях

Самое частое применение дженериков — это функции, которые работают с разными типами данных:

// Функция для работы с массивами
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const person = { name: 'Alice', age: 25, city: 'Moscow' };
const name = getProperty(person, 'name'); // Тип string
const age = getProperty(person, 'age');   // Тип number
// getProperty(person, 'salary'); // Ошибка! 'salary' не является ключом объекта

Generics в интерфейсах

Интерфейсы с дженериками позволяют создавать гибкие структуры данных:

// Обобщенный интерфейс для ответа API
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message?: string;
}

// Использование с разными типами
interface User {
  id: number;
  name: string;
}

const userResponse: ApiResponse<User> = {
  success: true,
  data: { id: 1, name: 'John' }
};

const listResponse: ApiResponse<string[]> = {
  success: true,
  data: ['apple', 'banana', 'cherry']
};

Generics в классах

Классы с дженериками позволяют создавать переиспользуемые компоненты:

// Простой стек с дженериками
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];
  }

  isEmpty(): boolean {
    return this.items.length === 0;
  }
}

// Использование
const numberStack = new Stack<number>();
numberStack.push(10);
numberStack.push(20);
console.log(numberStack.pop()); // 20

const stringStack = new Stack<string>();
stringStack.push('Hello');
console.log(stringStack.peek()); // Hello

Расширенные возможности Generics

Условные типы (Conditional Types)

TypeScript позволяет создавать типы, которые зависят от условий:

type IsString<T> = T extends string ? 'yes' : 'no';

type Test1                

Блоги

Рекомендаций по книгам