Skip to Content
TypeScriptДженерики

Дженерики

Уровень: middle.

Вопрос

Зачем функции параметр типа, если можно принять any и вернуть any?

Короткий ответ

Дженерик связывает вход и выход. Функция identity<T>(value: T): T возвращает тот же тип, что получила. С any эта связь пропадает, и вызывающий код теряет автодополнение.

Ограничение T extends { id: string } разрешает только значения с нужным полем и внутри функции даёт читать id.

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] { return items.map((item) => item[key]) }

Здесь K зависит от T: ключ обязан существовать, а массив на выходе содержит тип этого поля.

Разбор

Компонент списка хранит элементы вызывающего, а не «какие-то объекты»:

type ListProps<T> = { items: T[] getKey: (item: T) => string renderItem: (item: T) => string }

Вызов с User[] не даст случайно передать getKey от другого типа.

Значение по умолчанию, T = unknown, нужно редко: когда параметр не выводится из аргументов. Если вывод и так работает, явный аргумент типа на вызове только шумит.

На что смотрят на senior

Дженерик на всю глубину ответа API часто хуже узкого типа экрана. Параметр типа оправдан, когда библиотека или общий компонент не знает предметную область. В фиче достаточно конкретного User или Order.

Слишком широкий T extends Record<string, unknown> почти ничего не обещает. Ограничение должно отражать операцию: сравнение, ключ, рендер.

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

Типичные ошибки

  • Ставить <T> на функцию, которая всегда работает с одним типом.
  • Возвращать T из функции, которая на самом деле возвращает кусок T или Promise<T>.
  • Ограничивать дженерик через any внутри тела и терять смысл параметра.

Задача

Что вернёт тип вызова и зачем здесь два параметра типа, а не один?

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] { return items.map((item) => item[key]) } const ids = pluck([{ id: 1, name: 'a' }], 'id')
Показать ответ

T выводится как { id: number; name: string }, K как 'id'. Результат T[K][], то есть number[].

Один параметр T не связал бы ключ с типом поля. K extends keyof T не даёт передать чужой ключ, а T[K] возвращает тип именно этого поля, не any.

Last updated on