PurpleSchool — курсы программирования онлайн
  • Пути
    • Frontend React разработчик
    • Frontend Vue разработчик
    • Backend разработчик Node.js
    • Fullstack разработчик React / Node.js
    • Mobile разработчик React Native
    • Backend разработчик Golang
    • Devops инженер
    • Backend разработчик Python
  • AI для кодаНовое
  • О нас
    • Отзывы
    • Реферальная программа
    • О компании
    • Контакты
  • Иконка открытия меню
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • AI тренажёр
    • Проекты
PurpleSchool — платформа бесплатных roadmap и курсов для разработчиков
ютуб иконка
Telegram иконка
VK иконка
VK иконка
Курсы
ГлавнаяКаталог курсовFrontendBackendFullstack
Практика
КарьераПроектыPurpleПлюс
Материалы
БлогБаза знаний
Документы
Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
Реквизиты
ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

PurpleSchool © 2020 -2026 Все права защищены

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развитияВопросы для собеседований
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • PurpleSchool — курсы программирования онлайн
    • AI для кодаНовое
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    TypeScript дженерики на практике: примеры и лучшие приёмы

    TypeScript дженерики на практике: примеры и лучшие приёмы

    Аватар автора TypeScript дженерики на практике: примеры и лучшие приёмы

    Антон Ларичев

    Иконка календаря11 сентября 2026
    typescriptдженерикиgenericsтипизацияmiddleИконка уровня middle
    Картинка поста TypeScript дженерики на практике: примеры и лучшие приёмы

    Введение

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

    В этой статье разберём дженерики на конкретных примерах: от простых функций до сложных API-клиентов и React-хуков.

    Базовый синтаксис

    Дженерик — это переменная типа, которая подставляется при вызове функции или создании экземпляра класса.

    // T — параметр типа, TypeScript сам выведет его из аргумента
    function identity<T>(value: T): T {
      return value;
    }
    
    const num = identity(42); // T выводится как number
    const str = identity('hello'); // T выводится как string
    

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

    Ограничения типов (constraints)

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

    interface HasId {
      id: number;
    }
    
    // T должен обязательно содержать поле id
    function findById<T extends HasId>(items: T[], id: number): T | undefined {
      return items.find((item) => item.id === id);
    }
    
    const users = [{ id: 1, name: 'Аня' }, { id: 2, name: 'Игорь' }];
    const user = findById(users, 1); // тип выводится автоматически
    

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

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

    Дженерики отлично подходят для описания обёрток над данными — например, ответа API.

    interface ApiResponse<T> {
      data: T;
      error: string | null;
      status: number;
    }
    
    // Конкретное использование с типом Product
    interface Product {
      id: number;
      title: string;
      price: number;
    }
    
    async function fetchProduct(id: number): Promise<ApiResponse<Product>> {
      const res = await fetch(`/api/products/${id}`);
      const data = await res.json();
      return { data, error: null, status: res.status };
    }
    

    Теперь ApiResponse<T> можно переиспользовать для любого эндпоинта, просто подставляя нужный тип данных.

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

    Дженерик может принимать сразу несколько типов, если это отражает реальную структуру данных.

    // K — ключи объекта, V — значения
    function mapValues<K extends string, V, R>(
      obj: Record<K, V>,
      fn: (value: V) => R
    ): Record<K, R> {
      const result = {} as Record<K, R>;
      for (const key in obj) {
        result[key] = fn(obj[key]);
      }
      return result;
    }
    
    const prices = { apple: 100, banana: 50 };
    const withTax = mapValues(prices, (price) => price * 1.2);
    // withTax: Record<'apple' | 'banana', number>
    

    Значения по умолчанию для дженериков

    Если параметр типа не передан явно, можно задать значение по умолчанию.

    interface Pagination<T = unknown> {
      items: T[];
      page: number;
      total: number;
    }
    
    // Без явного указания типа получим Pagination<unknown>
    const empty: Pagination = { items: [], page: 1, total: 0 };
    
    // С явным типом — Pagination<Product>
    const products: Pagination<Product> = { items: [], page: 1, total: 0 };
    

    Дженерики в React-хуках

    Один из самых практичных случаев — типизация переиспользуемых хуков.

    import { useState, useCallback } from 'react';
    
    // Хук работает с любым типом данных, который в него передадут
    function useToggleList<T>(initial: T[] = []) {
      const [items, setItems] = useState<T[]>(initial);
    
      const add = useCallback((item: T) => {
        setItems((prev) => [...prev, item]);
      }, []);
    
      const remove = useCallback((predicate: (item: T) => boolean) => {
        setItems((prev) => prev.filter((item) => !predicate(item)));
      }, []);
    
      return { items, add, remove };
    }
    
    // Использование с конкретным типом
    const { items, add, remove } = useToggleList<Product>([]);
    

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

    Условные типы совместно с дженериками

    Дженерики часто комбинируют с условными типами для более точного вывода.

    type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
    
    type A = UnwrapPromise<Promise<string>>; // string
    type B = UnwrapPromise<number>; // number
    

    Эта техника лежит в основе многих встроенных утилитарных типов, включая Awaited<T>.

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

    Первая ошибка — использование дженерика там, где достаточно union-типа. Если функция принимает только string | number, не нужно писать <T extends string | number>, это лишняя абстракция.

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

    Третья ошибка — подавление ошибок типов через as any внутри дженерик-функции. Это ломает саму идею типобезопасности и может привести к рантайм-ошибкам, которые TypeScript должен был предотвратить.

    Четвёртая ошибка — слишком широкие ограничения. Например, <T extends object> часто пропускает больше, чем нужно, и стоит сузить его до конкретного интерфейса с нужными полями.

    Пятая ошибка — игнорирование вывода типов. TypeScript умеет выводить параметр типа из аргументов, и явное указание identity<string>('hello') в большинстве случаев избыточно.

    Заключение

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

    Иконка глаза297

    Комментарии

    0

    Постройте личный план изучения Основы разработки до уровня Middle — бесплатно!

    Основы разработки — часть карты развития Frontend, Backend, Mobile

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    Основы Git

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    HTML и CSS

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    CSS Flexbox

    Антон Ларичев
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    бесплатно
    Подробнее

    Похожие статьи

    Картинка поста Claude без VPN в России: как пользоваться в 2026
    Иконка аватараАнтон
    Иконка календаря16 октября 2026
    AnthropicAI-инструментыClaude CodejuniorИконка уровня junior

    Claude без VPN в России: как пользоваться в 2026

    Сайт claude.ai и приложения Claude из России без VPN не работают: Anthropic не включает Россию в список поддерживаемых регионов и закрывает доступ и к чату, и к API. Без VPN Claude доступен одним способом — через API-посредника, который сам обращается к Anthropic из поддерживаемой страны, а вам выдаёт ключ с оплатой в рублях. Так работает AI для кода от PurpleSchool: ключ подключается к Claude Code, VS Code, Cursor и другим инструментам разработчика, а также к вашему собственному коду. Окно чата как на claude.ai такой способ не даёт — это доступ к модели, а не к сайту.

    Иконка чипа0
    Иконка глаза13
    Иконка комментариев0
    Картинка поста Как настроить CI/CD пайплайн с нуля: полное руководство
    Иконка аватараАнтон
    Иконка календаря14 октября 2026
    CI/CDDevOpsGitHub Actions+ 2middleИконка уровня middle

    Как настроить CI/CD пайплайн с нуля: полное руководство

    CI/CD пайплайн с нуля: как собрать рабочий конвейер сборки, тестов и деплоя на примере GitHub Actions и Docker без лишней теории.

    Иконка чипа0
    Иконка глаза106
    Иконка комментариев0
    Картинка поста CLAUDE.md: как написать инструкции для Claude Code
    Иконка аватараАнтон
    Иконка календаря11 октября 2026
    AI-инструментыClaude CodeDevOps+ 1middleИконка уровня middle

    CLAUDE.md: как написать инструкции для Claude Code

    CLAUDE.md — это файл с инструкциями по проекту, который Claude Code читает в начале каждой сессии. Положите его в корень репозитория и опишите внутри, чем проект собирается, чем запускаются тесты и каких соглашений держаться в коде. После этого агент перестанет спрашивать одно и то же в каждом новом диалоге и предлагать решения, которые в вашей кодовой базе не приняты. Формат предельно простой: обычный Markdown, обязательных полей нет, заголовки любые. Дальше — где именно создавать файл и как Claude Code собирает инструкции сразу из нескольких мест, что писать внутрь, как подключать к файлу другие документы и чем CLAUDE.md отличается от AGENTS.md. Одна оговорка перед началом: Claude Code обновляется часто, набор команд и поддерживаемых файлов пополняется. Если какая-то деталь ниже ведёт себя у вас иначе, сверяйтесь с документацией той версии, которая стоит у вас.

    Иконка чипа0
    Иконка глаза327
    Иконка комментариев0
    Иконка чипа0