Хотите заказать веб-сайт? Связаться с нами

Дженерики в TypeScript: гибкие типы данных

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

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

Дженерики в TypeScript: гибкие типы данных

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

Дженерик — это тип или функция, которые принимают параметр типа в угловых скобках. Такой параметр ведёт себя как переменная, только вместо значения он хранит конкретный тип, который будет подставлен во время использования. Благодаря этому одна и та же конструкция подходит для множества разных типов данных без потери проверки типов.
function identity<T>(value: T): T {
  return value;
}
 
const num = identity<number>(42);
const str = identity("hello");

В примере выше T — это параметр типа функции identity. При явном вызове identity<number>(42) компилятор подставляет вместо T тип number. Во втором вызове тип не указан явно, но TypeScript сам выводит его из переданного аргумента благодаря выводу типов, поэтому str получает тип string.

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

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

function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}
 
const result = pair("id", 10);

Здесь параметры A и B независимы друг от друга: TypeScript выводит для result кортеж типа [string, number], сохраняя точную информацию о каждом элементе.

Зачем нужны дженерики

Без дженериков разработчику пришлось бы либо писать отдельную функцию для каждого типа данных, либо использовать тип any и полностью терять проверку типов. Дженерики решают обе проблемы сразу: код остаётся универсальным, а компилятор по-прежнему следит за соответствием типов на каждом шаге.

  1. Функция или тип описываются один раз и подходят для любых совместимых данных.
  2. Компилятор проверяет типы при каждом конкретном использовании, а не только на этапе написания кода.
  3. Редактор кода подсказывает точный тип результата вместо расплывчатого any.
Подход Что происходит с типами
any Проверка типов полностью отключается
Дженерик <T> Тип сохраняется и проверяется при каждом вызове

Если дженерик используется только один раз и его смысл очевиден из контекста, оставляйте короткое имя T. Но если в типе несколько параметров или их роль неочевидна, называйте их осмысленно, например TKey и TValue — это заметно упрощает чтение сложных типов в будущем.

Обобщённый тип для необязательных значений

Одна из частых задач — описать значение, которое либо содержит данные нужного типа, либо является null. Вместо того чтобы вручную дописывать | null к каждому отдельному типу в проекте, можно один раз создать переиспользуемый дженерик и использовать его везде.

type Nullable<T> = T | null;
 
let userName: Nullable<string> = null;
let userAge: Nullable<number> = 28;
 
userName = "Anjana";

Тип Nullable<T> подставляет вместо T любой конкретный тип и добавляет к нему null как допустимое значение. Такой подход избавляет от повторения одной и той же union-конструкции в разных частях кодовой базы и делает намерение разработчика более явным.

Ограничение параметра типа

Иногда параметр типа не должен принимать абсолютно любое значение. Например, если функция обращается к конкретному свойству объекта, компилятору нужно гарантировать, что это свойство точно существует. Для этого используется ключевое слово extends, которое задаёт ограничение на параметр типа.

function getLength<T extends { length: number }>(value: T): number {
  return value.length;
}
 
getLength("text");
getLength([1, 2, 3]);

Здесь T может быть любым типом, но только при условии, что у него есть свойство length с типом number. Если передать значение без такого свойства, TypeScript сообщит об ошибке ещё до запуска кода, а не во время работы приложения.

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

Как и обычным параметрам функции, параметру типа можно задать значение по умолчанию. Тогда при использовании дженерика без явного указания типа TypeScript подставит именно то значение, которое задано по умолчанию.

interface Box<T = string> {
  value: T;
}
 
const textBox: Box = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

В случае с textBox параметр T не указан явно, поэтому используется значение по умолчанию — string. А запись Box<number> явно переопределяет значение по умолчанию, задавая типу value число вместо строки.

Дженерики в интерфейсах

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

interface ApiResponse<T> {
  data: T;
  success: boolean;
}
 
const response: ApiResponse<string[]> = {
  data: ["first", "second"],
  success: true,
};

Такой интерфейс можно переиспользовать для описания любого ответа API: достаточно подставить нужный тип данных вместо T при объявлении конкретной переменной или параметра функции.

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

Итоги

Дженерики позволяют один раз описать функцию, тип или интерфейс, которые остаются строго типизированными при работе с разными видами данных. Параметр типа в угловых скобках, ограничения через extends, значения по умолчанию и обобщённые интерфейсы вроде ApiResponse<T> — это база, которая пригодится практически в любом TypeScript-проекте.

В следующем уроке разберём утилитарные типы Partial, Pick и Omit, которые помогают создавать новые типы на основе уже существующих, не переписывая их с нуля.

Теги: