any — это не тип, это капитуляция

Я клялась, что не буду писать на TypeScript. Потом понадобился фронт для catsh. Через неделю репозиторий был в any, а компилятор — декорация. Рассказываю, чем я его заменила, и даю три вопроса, на которых я сама когда-то провалилась.

Почему any кажется спасением

Rust меня избаловал: если собралось — скорее всего работает. TypeScript сначала казался тем же, только добрее. Пока я не начала лепить as any «на минутку». Минутка стала файлом. Файл стал модулем. Компилятор перестал быть собеседником.

any выключает проверку не в одном месте, а во всей цепочке. Передал any в функцию — она тоже заражена. Это не «временно», это дыра.

src/feed.ts — как не надо
function parsePost(raw: any) {
  return {
    title: raw.title.toUpperCase(),
    tags:  raw.tags.map((t: any) => t.trim()),
  };
}

parsePost({ title: 42 }); // компилируется. падает в рантайме

unknown + сужение

unknown — это «я не знаю, что тут», но честно. С unknown нельзя вызвать метод, пока не проверишь. Для JSON с сети это единственный взрослый вход.

src/feed.ts
function isPost(v: unknown): v is { title: string; tags: string[] } {
  if (!v || typeof v !== "object") return false;
  const o = v as Record<string, unknown>;
  return typeof o.title === "string" && Array.isArray(o.tags);
}

function parsePost(raw: unknown) {
  if (!isPost(raw)) throw new Error("bad post");
  return { title: raw.title.toUpperCase(), tags: raw.tags };
}
💡 Правило со стикера
Граница системы (fetch, localStorage, postMessage) — всегда unknown. Внутри модуля — конкретные типы. any только если обёртка над JS-библиотекой без типов, и даже тогда — в одном файле-адаптере.

satisfies вместо as

as — это «поверь мне». satisfies — «проверь, что объект подходит, но не сужай литералы». Для конфигов — золото.

src/theme.ts
const theme = {
  bg: "#0d0f14",
  accent: "#7c8fff",
} satisfies Record<string, string>;

theme.bg;      // тип остаётся "#0d0f14", не просто string
theme.missing; // ошибка компиляции — и правильно

Проверь себя

Три вопроса из тех, на которых я спотыкалась в первую неделю. Кликни ответ — сразу скажу, попала или нет.

Вопрос 1 из 3
Что произойдёт в рантайме?
scratch.ts
const x: any = "Молли";
x.purr();
any выключает проверку. Компилятор молчит. В рантайме строка — это String, метода purr нет. TypeError. Именно поэтому any опаснее, чем кажется.
Вопрос 2 из 3
Чем unknown отличается от any?
Оба значат «что угодно». Разница: any разрешает любые операции сразу, unknown — нет. Сначала typeof, type guard или литеральная проверка.
Вопрос 3 из 3
Зачем satisfies, если есть аннотация типа?
const t: Record<string, string> = { bg: "#0d0f14" } сделает t.bg просто string. satisfies оставляет "#0d0f14". Автодополнение и исчерпывающие проверки остаются острыми.

Итог

Strict-режим, noImplicitAny, никаких any в прикладном коде. Адаптеры к чужому JS — отдельная папка. Через две недели я перестала скучать по Python на фронте. Почти.

Компилятор, который можно обмануть одной буквой, — это не компилятор. Это комментарий.

Была ли статья полезной?