Программирование с ИИ
ENRU
9.4

Как читать чужие типы

union, сужение, дженерики, утилитные типы

Что это

Типы, которые пишет ИИ или библиотека, выглядят сложнее тех, что пишешь ты сам: угловые скобки, вертикальные черты, незнакомые слова. Читаются они проще, чем кажутся, — надо знать четыре конструкции.

Эта статья — про чтение. Писать такие типы с нуля не придётся.

Как выглядит в коде

Union и сужение

type Result =
  | { ok: true; data: string[] }
  | { ok: false; error: string };

function show(r: Result): string {
  if (r.ok) return `получено ${r.data.length}`;
  return `ошибка: ${r.error}`;
}
измерено
show({ ok: true, data: ["Oslo", "Bergen"] })  ->  получено 2
show({ ok: false, error: "таймаут" })         ->  ошибка: таймаут

Читается так: Result — это либо объект с ok: true и data, либо объект с ok: false и error.

Самое интересное — что происходит внутри if. После проверки r.ok компилятор знает, какой из двух вариантов перед ним: в первой ветке есть data и нет error, во второй наоборот. Обратишься к r.error в первой ветке — ошибка компиляции.

Это называется сужение типа (narrowing), и это главное, ради чего union используют. Такая пара «флаг плюс данные» — стандартный способ вернуть «получилось или нет», не бросая исключение.

Сужение делают и обычные проверки:

ПроверкаЧто сужает
if (x === null)убирает null из union
typeof x === "string"оставляет строки
Array.isArray(x)оставляет массивы
if ("data" in r)по наличию поля

Дженерики

function firstOr<T>(items: T[], fallback: T): T {
  return items.length > 0 ? items[0]! : fallback;
}
измерено
firstOr(["Oslo"], "нет")   ->  Oslo      (T = string)
firstOr([10, 20], 0)       ->  10        (T = number)

<T> — не конкретный тип, а заполнитель: «какой тип придёт, такой и вернётся». Благодаря этому функция работает с любыми данными и при этом не теряет тип: результат firstOr([10, 20], 0) — число, а не «что-то».

Читать дженерики надо как шаблон с дыркой: подставь вместо T конкретный тип из вызова, и всё станет понятно.

Встречаются постоянно: Array<string> (то же, что string[]), Promise<City> (промис, который вернёт City), Map<string, number>.

Утилитные типы

Готовые преобразования типов. Четыре, которые покрывают почти всё:

interface City { name: string; temp: number; country: string }

type CityPatch     = Partial<City>;              // все поля необязательны
type CityKey       = Pick<City, "name" | "temp">;// только эти
type CityNoCountry = Omit<City, "country">;      // все, кроме
type ByName        = Record<string, City>;       // словарь
ЗаписьЧитается как
Partial<T>«все поля T, но необязательные»
Pick<T, K>«только поля K из T»
Omit<T, K>«все поля T, кроме K»
Record<K, V>«словарь: ключи K, значения V»

Partial чаще всего встречается у функций обновления: прислать можно часть полей. Record<string, City> — то же, что dict[str, City] в Python.

Что ещё встретится

unknown — «тип неизвестен, и пока не проверишь, ничего сделать нельзя». Правильная замена any: заставляет проверить.

as const — «это значение не изменится», превращает "paid" из просто строки в точное значение.

keyof City — union из имён полей: "name" | "temp" | "country".

Как сказать ИИ

Запрос
Объясни этот тип по частям, как будто я вижу дженерики
впервые. Что сюда подставляется и что получается на выходе.
Запрос
Перепиши эту функцию так, чтобы она возвращала
{ ok: true, data } | { ok: false, error } вместо того,
чтобы бросать исключение.
Запрос
Здесь три почти одинаковых интерфейса. Можно ли выразить их
через Pick/Omit от одного базового? Покажи, как.

На что смотреть

Union без сужения. Если тип A | B, а в коде ни одной проверки — значит, компилятор заставили замолчать через as.

Дженерик, который везде any. <T = any> отключает всю пользу.

Типы продублированы. Три интерфейса с перекрывающимися полями вместо Pick/Omit от одного. Разъедутся при первой правке.

unknown, которое сразу приводят через as. Смысл unknown в том, чтобы проверить, а не в том, чтобы обойти.

Слишком умный тип. Если тип занимает десять строк и его никто не может объяснить — он сложнее задачи. ИИ иногда увлекается.

Шпаргалка

  • A | B — либо одно, либо другое; проверка сужает до одного.

  • Пара { ok: true, data } | { ok: false, error } — стандартный «получилось или нет».

  • <T> — заполнитель: какой тип пришёл, такой и вернётся.

  • Partial необязательные, Pick только эти, Omit все кроме, Record словарь.

  • unknown — честная замена any: заставляет проверить.

  • Не понял тип — наведи курсор, редактор покажет развёрнутый вид.