Урок по треку TypeScript 6 мин

Дженерики без страха

Параметры типов, ограничения extends, вывод и значения по умолчанию.

Дженерик это параметр типа. Как обычный параметр функции принимает значение, параметр типа принимает тип, и связывает типы входа и выхода.

Зачем

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.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.

Вопросы модуля (49)