Дженерики
Вопрос
Зачем функции параметр типа, если можно принять 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.