Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Дженерики (Типы) Продвинутый Typescript

В этом уроке поговорим подробнее про Generic Types. Возьмем для примера массив.

Массив — это тип-контейнер, который хранит внутри себя значения любого указанного типа. Логика работы массива не зависит от типа данных, хранящихся внутри. Такое определение автоматически говорит о том, что мы имеем дело с обобщенным типом.

Чтобы работать с таким типом, нужно конкретизировать внутренний тип в тот момент, когда мы хотим начать работу с данными этого типа:

const numbers: Array<number> = [];
numbers.push(1);

const strings: Array<string> = [];
numbers.push('hexlet');

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

Представим, что мы хотим определить свою коллекцию, которая работает как массив, но с дополнительными возможностями. Такие коллекции часто делают в ORM для работы с данными, загруженными из базы. Опишем сначала конкретную версию этого типа, работающую только с числами и парой стандартных методов:

type MyColl = {
  data: Array<number>;
  forEach(callback: (value: number, index: number, array: Array<number>) => void): void;
  at(index: number): number | undefined;
}

Здесь мы видим, что данные коллекции хранятся в числовом массиве. При этом в типе определено два метода:

  • Метод forEach передает элементы коллекции в колбек
  • Метод at возвращает элементы коллекции по указанному индексу

Одна из возможных реализаций этого типа может выглядеть так:

// Типы можно не прописывать, потому что они указаны в MyColl
const coll: MyColl = {
  data: [1, 3, 8],
  forEach(callback) {
    this.data.forEach(callback);
  },
  at(index) {
    return this.data.at(index); // target >= ES2022
  },
}

coll.at(-1); // 8

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

  • для элементов коллекции вместо number написать T или любое другое имя, начинающееся с большой буквы
  • добавить T как параметр типа к определению

Так это работает на практике:

type MyColl<T> = {
  data: Array<T>;
  forEach(callback: (value: T, index: number, array: Array<T>) => void): void;
  at(index: number): T | undefined;
}

На такое определение типа можно смотреть как на своеобразное определение функции. Для примера попробуем указать конкретный тип — например, MyColl<string>. В таком случае T заменяется на string внутри определения типа. Причем если внутри типа используются другие дженерики, то они вызывают тип дальше. Другими словами, все это работает как вложенные вызовы функций.

Ограничения дженериков

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

interface HasId {
  id: number;
}

type MyColl<T extends HasId> = {
  data: Array<T>;
  forEach(callback: (value: T, index: number, array: Array<T>) => void): void;
  at(index: number): T | undefined;
}

Это позволяет нам использовать тип MyColl только с типами, которые реализуют интерфейс HasId. Например, такой код не будет работать:

const coll: MyColl<number> = {
  data: [1, 3, 8],
  forEach(callback) {
    this.data.forEach(callback);
  },
  at(index) {
    return this.data.at(index); // target >= ES2022
  },
}

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


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 9 мая
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 9 мая
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 9 мая

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»