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

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

    Комментарии

    0

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

    Angular — часть карты развития Frontend

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

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

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

    Основы разработки

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

    Основы Git

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

    HTML и CSS

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

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

    Картинка поста OpenCode: пользовательские провайдеры недоступны — что делать
    Иконка аватараАнтон
    Иконка календаря26 октября 2026
    AI-инструментыapiразработкаjuniorИконка уровня junior

    OpenCode: пользовательские провайдеры недоступны — что делать

    Ошибка «Пользовательские провайдеры недоступны на этом сервере» (в английском интерфейсе — «Custom providers are unavailable on this server») появляется в OpenCode Desktop и веб-интерфейсе OpenCode, когда вы сохраняете своего провайдера через «Настройки → Провайдеры → Пользовательский провайдер». Это ошибка самого OpenCode, а не вашего ключа, сервера или региона: в версиях 2.0.x форма сохранения не подключена к серверу и выдаёт эту ошибку при любых введённых данных. VPN и смена ключа не помогут. Решение — описать провайдера в файле `opencode.json`: так он работает и в Desktop, и в терминальной версии. Ниже — что именно сломано, готовые конфиги для Claude, GPT и Qwen и что делать, если модели не появились.

    Иконка чипа0
    Иконка глаза21
    Иконка комментариев0
    Картинка поста Как тратить меньше токенов в Claude Code
    Иконка аватараАнтон
    Иконка календаря25 октября 2026
    Claude CodeAnthropicAI-инструментыmiddleИконка уровня middle

    Как тратить меньше токенов в Claude Code

    Чтобы Claude Code тратил меньше токенов, сократите контекст, который он пересылает с каждым запросом, и не ломайте кэш посреди задачи. На практике это пять привычек: `/clear` между несвязанными задачами, `/compact` в паузах, а не посреди работы, Sonnet вместо Opus для повседневного кода, выбор модели в начале сессии, а не на середине, и короткий `CLAUDE.md`. Дороже всего в счёте обходится не длина ваших вопросов, а новый контекст и пересборка кэша — именно их эти привычки и сокращают.

    Иконка чипа0
    Иконка глаза107
    Иконка комментариев0
    Картинка поста Qwen 3.8 Max для кода: как подключить из России
    Иконка аватараАнтон
    Иконка календаря24 октября 2026
    AI-инструментыapiразработкаjuniorИконка уровня junior

    Qwen 3.8 Max для кода: как подключить из России

    Qwen 3.8 Max — флагманская модель Alibaba с контекстом в 1 млн токенов и ответом до 131 тыс. токенов, которую Alibaba позиционирует прежде всего для программирования. Работать с ней из России проще всего в Qwen Code или OpenCode через ключ AI для кода от PurpleSchool: оплата в рублях, без VPN и без аккаунта Alibaba Cloud. Модель подключается по протоколу OpenAI Responses API — ниже готовые конфиги для обоих клиентов, характеристики модели и частые ошибки.

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