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-хуках и работе с коллекциями. Ключевое правило — применять дженерик только тогда, когда параметр типа действительно влияет на несколько мест в сигнатуре или структуре. Если это не так, обычный тип будет проще для чтения и поддержки. Освоив ограничения, значения по умолчанию и комбинацию с условными типами, вы сможете писать переиспользуемый код, который остаётся полностью типобезопасным.

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

    Комментарии

    0

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

    TypeScript с нуля - полный курс и паттерны проектирования — часть карты развития Frontend, Backend, Mobile

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

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

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

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

    React и Redux Toolkit

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

    Zustand

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

    Neovim

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

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

    Картинка поста Vue vs React в 2026: какой фреймворк выбрать для проекта
    Иконка аватараАнтон
    Иконка календаря10 сентября 2026
    VueReactFrontend+ 1middleИконка уровня middle

    Vue vs React в 2026: какой фреймворк выбрать для проекта

    Сравниваем Vue и React в 2026 году: реактивность, Composition API vs хуки, Server Components и Vapor Mode на примерах кода.

    Иконка чипа0
    Иконка глаза51
    Иконка комментариев0
    Картинка поста Подписка Claude Pro, Max или API: что дешевле разработчику
    Иконка аватараАнтон
    Иконка календаря08 сентября 2026
    claudeanthropicapi+ 2middleИконка уровня middle

    Подписка Claude Pro, Max или API: что дешевле разработчику

    Подписка Claude Pro (около 20$/мес) закрывает нужды одиночной разработки в чате, а Max и API открывают доступ к Claude Code и агентам — разбираемся, что выгоднее по деньгам.

    Иконка чипа0
    Иконка глаза143
    Иконка комментариев0
    Картинка поста Claude Code не запускается: 7 частых ошибок и способы их решения
    Иконка аватараАнтон
    Иконка календаря06 сентября 2026
    Claude CodeAI для разработчиковотладка+ 1middleИконка уровня middle

    Claude Code не запускается: 7 частых ошибок и способы их решения

    Claude Code не работает: разбираем типичные причины — от истёкшего токена и старой версии Node.js до конфликтов в settings.json и проблем с MCP-серверами.

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