Дженерик это параметр типа. Как обычный параметр функции принимает значение, параметр типа принимает тип, и связывает типы входа и выхода.
Зачем
function firstAny(arr: any[]): any { return arr[0]; }
function first<T>(arr: T[]): T | undefined { return arr[0]; }
const n = first([1, 2]); // number | undefined
const s = first(['a']); // string | undefined
С any связь между входом и выходом теряется. С T компилятор помнит, что вернётся элемент того же типа.
Вывод параметров
Обычно T не пишут явно: TypeScript выводит его из аргументов. Явно указывают, когда вывести не из чего: useState<User | null>(null), new Map<string, number>().
Ограничения: extends
T extends X значит «T должен быть совместим с X». Внутри функции можно пользоваться тем, что гарантирует X.
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest('abc', 'de'); // ок
longest([1], [2, 3]); // ок
longest(1, 2); // ошибка: у number нет length
Связка с keyof:
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
get({ id: 1, name: 'a' }, 'name'); // string
get({ id: 1 }, 'nope'); // ошибка
Значения по умолчанию
type ApiResponse<T = unknown> = { data: T; status: number };
const r: ApiResponse = { data: 1, status: 200 }; // data: unknown
Дженерики в типах, интерфейсах и классах
interface Box<T> { value: T }
class Store<S> {
constructor(private state: S) {}
get(): S { return this.state; }
}
const-параметры типа
С TypeScript 5.0 модификатор const просит выводить литеральные типы, как будто аргумент передан с as const:
function routes<const T extends readonly string[]>(r: T) { return r; }
const r = routes(['/a', '/b']); // readonly ['/a', '/b']
Частые ошибки
- Параметр типа, который встречается один раз:
function log<T>(x: T): void. Он ничего не связывает, прощеx: unknown. - Ограничение шире, чем нужно, или наоборот,
T extends any. - Возврат
T, когда на деле создаётся новый объект другой формы:function f<T extends {a: number}>(): T { return { a: 1 } }не компилируется, потому чтоTможет иметь больше полей. - Явное
as Tвнутри, чтобы «заткнуть» компилятор.
Что спросят на собесе
- Зачем дженерики, если есть
any. - Что значит
T extends keyof Uи как типизироватьget(obj, key). - Когда параметр типа выводится, а когда его указывают явно.
- Можно ли задать значение по умолчанию параметру типа.
- Как дженерик-компонент или хук типизируют в React.