Gernar
Frontend DeveloperTypeScript

Интервью-вопрос

Что такое дженерики

Дженерики позволяют передавать тип как параметр и сохранять строгую типизацию в переиспользуемом коде. На практике важно не путать их с `any` и помнить, что они не проверяют данные в runtime.

Добавлен
Редакция

Подготовьте короткий ответ и пару деталей на случай уточняющих вопросов.

🐰0
🥚0

Мини-квиз

Проверка перед разбором

Несколько быстрых вопросов перед разбором. Так проще поймать места, которые только кажутся понятными.

Вопрос 1 из 50 правильно

Как лучше коротко объяснить дженерики?

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

Варианты ответа

Разбор

Разобраться, а не зазубрить

Дальше разбираем суть, типичные уточнения и места, где легко сказать лишнее или перепутать термины.

Базовая идея

Дженерик можно воспринимать как переменную для типа. Обычная переменная хранит значение, а generic-параметр хранит тип, который TypeScript подставит при использовании.

Самый простой пример показывает не саму функцию, а связь между входом и выходом:

function identity<T>(value: T): T {
  return value;
}

const name = identity("Ada"); // string
const count = identity(3); // number

Здесь функция одна, но TypeScript не теряет конкретный тип. Если бы вместо T был any, редактор хуже подсказывал бы свойства, а ошибки могли бы пройти до runtime.

Как лучше сказать на интервью

Можно ответить так:

Дженерики это способ параметризовать код типом. Я могу написать универсальную функцию или интерфейс, а конкретный тип передать или вывести при использовании. Это помогает не дублировать код и не уходить в any, потому что TypeScript сохраняет связь между аргументами, результатом и полями объекта.

После этого добавьте пример из своей практики. Для frontend-разработчика удобно говорить про API-ответ, хук, форму, таблицу или список. Так ваш ответ звучит не как определение из учебника, а как рабочее понимание инструмента.

Где это полезно во frontend-коде

Частый пример - общая оболочка ответа API. Структура ответа одинаковая, но поле data бывает разным.

type ApiResponse<T> = {
  data: T;
  status: "success" | "error";
  message?: string;
};

type User = {
  id: string;
  name: string;
};

async function fetchJson<T>(url: string): Promise<ApiResponse<T>> {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("Request failed");
  }

  return response.json() as Promise<ApiResponse<T>>;
}

const result = await fetchJson<User>("/api/me");
result.data.name;

Польза в том, что ApiResponse не нужно копировать для каждого ресурса. Но есть важная граница: fetchJson<User> не проверяет, что сервер реально вернул User. Приведение типа после response.json() только сообщает TypeScript, чему вы доверяете. Оно не защищает UI от пустого ответа, HTML вместо JSON или поля другого формата. В реальном компоненте также обработайте loading, error и empty state. Если ответ приходит из внешнего API или влияет на деньги, права доступа или важный UI, добавьте runtime-валидацию.

Ограничения через extends

Generic без ограничений может быть чем угодно. Поэтому TypeScript не даст обратиться к свойству, которое не гарантировано.

Плохой пример:

function getId<T>(item: T) {
  return item.id;
}

Такой код не пройдет строгую проверку TypeScript. Если обойти ошибку через as any, в runtime можно получить undefined, неверный ключ в списке React или запрос на URL с пустым id. Правильнее описать минимальный контракт:

function getId<T extends { id: string }>(item: T): string {
  return item.id;
}

getId({ id: "u1", name: "Ada" });

Теперь функция все еще универсальная. Она принимает любой объект с дополнительными полями, но требует id. Это хорошая формулировка для интервью: generic не означает "любой без правил", он может быть ограничен контрактом.

Как выбрать между generic, union и overload

Как выбрать подход

1Нужно сохранить тот же тип на входе и выходе?
Используйте дженерик, например `function identity<T>(value: T): T`.
2Есть несколько заранее известных вариантов?
Часто достаточно union-типа, например `"idle" | "loading" | "error"`.
3Поведение реально разное для разных сигнатур?
Рассмотрите перегрузки, чтобы описать разные входы и результаты.
4Вы хотите просто отключить ошибки типизации?
Не заменяйте проблему дженериком. Найдите точный тип или добавьте runtime-проверку.

На интервью не стоит говорить, что дженерики нужны везде. Они нужны там, где тип должен подставляться и сохраняться. Если у вас фиксированный список значений, union обычно проще. Если разные сигнатуры ведут себя по-разному, иногда честнее описать перегрузки.

Пример, где union лучше generic:

type LoadStatus = "idle" | "loading" | "success" | "error";

Здесь нет смысла делать абстрактный параметр типа. Вам нужна не универсальность, а строгий список допустимых состояний. Это снижает риск опечаток, лишних состояний и неправильной обработки UI.

Типичная ловушка с API

Самая частая ошибка в ответе - сказать, что generic делает данные безопасными. TypeScript проверяет ваш код до запуска, но не проверяет реальный ответ сервера.

const user = await fetchJson<User>("/api/me");

// TypeScript верит, что name есть.
user.data.name.toUpperCase();

Если сервер вернул { "username": "ada" }, типизация не спасет от ошибки логики. UI может упасть на рендере профиля или показать пустое имя без понятной ошибки. Безопаснее проверять форму ответа до записи в состояние и отдельно показывать loading, error и empty state. Если компонент делает запрос в useEffect, generic тоже не отменяет запрос и не защищает от race condition. Для этого нужны AbortController, cleanup или проверка актуальности ответа. Безопасный вывод такой: generic помогает описать ожидаемый контракт, но для внешних данных нужна проверка ответа, особенно если API нестабилен или контролируется другой командой.

Практический вывод

В сильном ответе держите три шага. Сначала дайте определение: параметр типа для универсального кода. Потом покажите пользу: сохраняется связь между типами без any. Затем назовите границу: для свойств нужен extends, а для данных из runtime нужна отдельная проверка.

Если вы пишете код на React и TypeScript, дженерики часто встречаются в хуках, компонентах списков, формах, таблицах и типах API. Но хороший frontend-код не обязан быть максимально абстрактным. Он должен быть понятным для команды и достаточно строгим, чтобы ловить ошибки до запуска приложения.

Частые ошибки

Где обычно ошибаются

Проверьте формулировки, которые звучат уверенно, но на интервью быстро выдают пробелы.

  1. 1

    Заменять `any` бессмысленным `T`

    Если тип-параметр нигде не связывает значения, он не добавляет безопасности. Например, функция, которая принимает T и всегда возвращает строку, часто не нуждается в дженерике. Лучше оставить конкретные типы и не создавать ложное ощущение универсальности.
  2. 2

    Не ставить ограничения там, где они нужны

    Если вы обращаетесь к value.id, TypeScript должен знать, что id существует. Для этого нужен T extends { id: string } или другой явный контракт. Иначе код либо не скомпилируется, либо вы начнете обходить типизацию через as any.
  3. 3

    Путать типизацию с валидацией данных

    fetchJson<User>() говорит TypeScript, каким типом вы считаете результат, но не проверяет ответ сервера. Если API вернет поле не того формата, баг проявится в UI уже в runtime. Для ненадежных данных добавляйте проверку схемой или ручной guard.
  4. 4

    Делать слишком сложные generic-типы

    Вложенные условные типы и длинные цепочки параметров могут быть точными, но плохо читаются. На интервью это звучит слабее, если вы не можете объяснить пользу. В рабочем коде лучше разделить типы на именованные части и оставить простой публичный API.
  5. 5

    Использовать generic там, где нужен union

    Если вариантов мало и они известны заранее, union часто проще и понятнее. Например, статус загрузки лучше описать как "idle" | "loading" | "success" | "error", а не как абстрактный TStatus. Так код легче читать и безопаснее расширять.

Follow-up

Что могут спросить дальше

Короткие ответы на вопросы, которыми проверяют понимание дженериков, ограничений и границ TypeScript.

Живые ответы

Видео с похожим вопросом

Если найдем публичные интервью с таким вопросом, добавим их сюда. Их удобно смотреть после теории, чтобы свериться с живыми ответами.

Пока видео нет. Когда появятся подходящие публичные интервью, добавим их в этот блок, чтобы можно было сравнить разбор с тем, как отвечают реальные кандидаты.

Содержание