> Что такое дженерики в TypeScript и для чего они используются (TypeScript)

Уровень: junior · Роль: frontend · Категория: Технические вопросы

Компании: Иннотех, Альфа-банк, Soft_Media_Group, ООО Премиум ИТ Солюшен, Kodix, IT-One, GetMatch, Арго, TYMY, LoyaltyLabs, SmartWay

Стек: TypeScript

> Пример ответа

Короткий ответ

Дженерики (generics) в TypeScript - это механизм, позволяющий создавать компоненты (функции, классы, интерфейсы), которые работают с разными типами данных, сохраняя при этом строгую типизацию. Они используются для повышения переиспользуемости кода и type safety без потери информации о конкретном типе. Например, функция identity<T>(arg: T): T возвращает значение того же типа, который был передан.

Подробное объяснение

Дженерики решают проблему, когда нужно написать функцию или класс, работающий с разными типами, но без использования any (который отключает проверку типов). Вместо того чтобы дублировать код для каждого типа (например, number, string, object), мы используем параметр типа - обычно обозначается как T, U, K и т.д.

Основные сценарии использования:

  • Типизированные коллекции: Array<T> - массив элементов типа T.
  • Функции с сохранением типа: функция, принимающая и возвращающая значение одного типа.
  • Constraints (ограничения): с помощью extends можно ограничить допустимые типы, например, T extends HasLength.
  • Generic constraints with keyof: доступ к свойствам объекта по ключу с проверкой типа.
  • Generic classes: классы, где тип полей или методов определяется при создании экземпляра.

Дженерики компилируются в обычный JavaScript без потери информации о типах - проверки происходят только на этапе компиляции.

На практике

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

  • React: типизация пропсов компонентов через React.FC<Props> или useState<T>().
  • API-клиенты: функция fetchData<T>(url): Promise<T> для типизации ответов.
  • Redux Toolkit: createSlice с generic для state и actions.
  • Utility types: Partial<T>, Pick<T, K>, Record<K, V> - все они дженерики.
  • Хранилища (store): generic-классы для работы с разными сущностями.

Пример из реального кода: функция для обновления состояния в Redux reducer:

TYPESCRIPT
function updateField<T extends Record<string, any>, K extends keyof T>(
obj: T,
key: K,
value: T[K]
): T {
return { ...obj, [key]: value };
}

Пример кода

TYPESCRIPT
// Базовая generic-функция
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
const num = firstElement([1, 2, 3]); // number
const str = firstElement(['a', 'b']); // string
// Generic-интерфейс
interface ApiResponse<T> {
data: T;
status: number;
error?: string;
}
// Generic-класс
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
}
// Constraints
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
getLength('hello'); // 5
getLength([1, 2, 3]); // 3
// getLength(123); // Ошибка: number не имеет length

Как отвечать на собеседовании

Начни с простого определения: "дженерики - это параметризованные типы, позволяющие писать код, который работает с разными типами, но сохраняет типовую безопасность". Приведи пример из стандартной библиотеки TypeScript - Array<T>. Затем объясни, чем это лучше any: дженерики не теряют информацию о типе, а any её отключает.

Если спросят про ограничения, упомяни extends и keyof. Хорошо показать понимание, что дженерики существуют только на этапе компиляции и не влияют на рантайм.

Что проверяет интервьюер

  • Понимание базовой концепции параметризации типов.
  • Умение отличать дженерики от any и от union-типов.
  • Знание синтаксиса: function<T>, interface<T>, class<T>.
  • Понимание constraints и их практического применения.
  • Способность объяснить, зачем это нужно в реальном проекте.

Типичные ошибки

  • Путать дженерики с any: "дженерики - это то же самое, что any, только с другим синтаксисом". Это неверно - any отключает проверку типов, дженерики её сохраняют.
  • Неправильно расставлять скобки: function<T> вместо function <T> (пробел необязателен, но важно соблюдать синтаксис).
  • Забывать про множественные параметры: function pair<T, U>(a: T, b: U): [T, U].
  • Использовать дженерики там, где достаточно конкретного типа или union-типа (overengineering).
  • Не понимать разницы между T extends SomeType и T = SomeType (constraint vs default type).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью