EverProduct
Фронтенд

Этап 05 · Ремесло

Сетка: типы как документация, которая не умеет врать

У каждой написанной тобой функции уже есть тип — просто ты держишь его в голове, где его никто не проверяет и где он потихоньку перестаёт быть правдой.

Ты пишешь user.adress. Одна пропущенная буква. JavaScript совершенно доволен: свойства нет, значит, значение undefined, и страница рисует строку адреса со словом «undefined» — в пятницу, в проде, на оформлении заказа.

Или функция принимает идентификатор и имя, кто-то зовёт её с именем и идентификатором, и раз оба строки — никто не возражает. Запись сохраняется с перепутанными полями, а узнаёшь ты об этом от клиента.

У обоих багов общее свойство: они не сложные, они просто невидимы до момента выполнения. Именно эту категорию убирает TypeScript.

Что это вообще такое

TypeScript — это JavaScript плюс аннотации, которые проверяются до запуска кода, а потом стираются. Браузер не видит ни одного типа; на выходе обычный JavaScript. Ни стоимости во время работы, ни защиты во время работы: вся проверка происходит на этапе сборки, в редакторе, по мере набора.

Отсюда формулировка, от которой всё встаёт на место: типы ты и так знаешь. Когда ты пишешь функцию, ты знаешь, что она принимает объект пользователя с идентификатором и именем и возвращает строку. Сейчас это знание живёт в голове, где его никто не проверяет и где оно тихо перестаёт быть правдой через три месяца после того, как кто-то переименовал поле.

Комментарий описывает то, во что кто-то однажды верил. Тип проверяется при каждой сборке.

Части, которые окупаются

Чтобы получить бо́льшую часть пользы, много языка не нужно.

Аннотируй края, середину оставь выводу типов. Параметры функций и возвращаемые типы писать стоит; локальные переменные — почти никогда, потому что TypeScript и так знает, что присвоенная строка делает переменную строкой. Переаннотирование — привычка новичка, добавляющая шум и ноль безопасности.

Опиши формы своих данных один раз. User, Order, Product — записанные в одном месте и используемые везде. Здесь заявление «документация, которая не расходится с кодом» становится настоящим: переименуй поле — и каждое место, где оно использовалось, немедленно загорится красным, а не всплывёт в отчёте об ошибках через месяц.

Объединения — лучшая часть. Значение, которое является ровно одним из 'loading' | 'error' | 'empty' | 'success', даёт зубы правилу «сделай недопустимые состояния невыразимыми» из статьи про состояние: теперь его обеспечивает компилятор, и он же скажет, что ты забыл обработать один из четырёх случаев. То же для вариантов, ролей, статусов — везде, где иначе были бы вольные строки и комментарий.

Строгая проверка на null — самый крупный выигрыш. С ней значение, которого может не быть, обязано быть объявлено таковым, и трогать его нельзя, пока не проверил. Одна эта настройка убирает бо́льшую часть семейства «cannot read properties of undefined», а это самая частая ошибка во фронтенде с большим отрывом.

Дженерики, без страха. Они означают всего лишь «работает с любым типом и помнит, с каким именно». Список пользователей возвращается списком пользователей, а не списком чего попало. Пользоваться ими ты начнёшь задолго до того, как напишешь свой, а когда напишешь — это обычно одна буква в угловых скобках.

Аварийные выходы и их честная цена

any выключает проверку для этого значения. Иногда это прагматично и всегда является дырой: всё, что течёт из any, тоже не проверяется, и кодовая база с парой небрежно поставленных any может иметь очень слабое реальное покрытие, выглядя типизированной.

unknown — честная версия: «я не знаю, что это», и компилятор заставит проверить до использования. Когда тянет на any, обычно имелся в виду unknown.

Утверждения типа — сказать компилятору «поверь, это User» — это обещания, а не проверки. Их никто не верифицирует. Иногда они необходимы и чаще всего именно из-за них «полностью типизированная» кодовая база оказывается лгуньей.

Граница: где типы — лишь надежда

Вот недоразумение, которое подводит людей, поэтому скажем прямо: объявление типа для данных с сервера не заставляет данные ему соответствовать.

Типы стираются. Компилятор проверил твой код относительно формы, которую ты объявил; самого ответа он не видел. Если API вернёт null там, где ты пообещал строку, TypeScript промолчит, и падение случится ровно там же, где и без типов.

Поэтому края приложения — ответы сети, локальное хранилище, параметры адреса, ввод формы, колбэки сторонних библиотек — это места, где типы перестают быть гарантией и становятся допущением. Есть два честных варианта: проверять на границе схемой во время выполнения, чтобы форма проверялась один раз на входе и дальше типизировалась с уверенностью, — или сознательно принять допущение и обработать сбой. Нечестно другое: верить, что объявление интерфейса защищает тебя от API, который тебе не подчиняется.

Настоящая выгода — это петля

Спроси того, кто прожил с TypeScript год, чего ему не хватало бы больше всего, — и ответом обычно будут не пойманные ошибки. Ответом будет редактор.

Автодополнение, знающее, что действительно есть у этого объекта. Переименование, корректно обновляющее девяносто четыре использования. Точный поиск всех обращений. Красное подчёркивание в тот момент, когда вызов перестал соответствовать функции, — до сохранения, до перезагрузки, до того, как ты забыл, чем занимался.

Это петля обратной связи, измеряемая миллисекундами, на задаче, которую ты выполняешь сотни раз в день. Описание осознанной практики в сфере «Как учиться» — немедленный сигнал, немедленное исправление — относится к инструментам не меньше, чем к учёбе, и это самая плотная петля во всём роадмапе.

Меняется и то, как ты читаешь незнакомый код: с типами можно понять контракт функции, не читая её тело, а это и делает большую кодовую базу проходимой.

Издержки, честно

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

И это не всегда оправдано. Скрипт на пятьдесят строк, разовая страница, прототип, который ты выбросишь через неделю, — обычный JavaScript подойдёт, и говорить об этом не ересь. Ценность растёт вместе с тем, сколько код живёт и сколько людей его трогает, — поэтому почти всё профессиональное сейчас типизировано.

Как это учить

Включи строгий режим с первого дня. Учиться с выключенным и включить потом — значит переписывать; а проверка на null, которую приносит строгий режим, и есть главное, ради чего всё затевалось.

Читай ошибки как предложения. Ошибки TypeScript знамениты длиной, и навык состоит в поиске первого настоящего несоответствия: обычно это самое внутреннее «тип X не совместим с типом Y» ближе к концу, где названы две вещи, которые реально расходятся. Как только ты научился их читать, компилятор становится преподавателем, который отвечает мгновенно и никогда не устаёт.

Типизируй существующий проект вместо изучения типов в вакууме. Возьми то, что писал на JavaScript, и добавь типы. Каждая найденная ошибка — настоящий баг или настоящая двусмысленность в коде, который ты уже понимаешь, а это куда лучший материал, чем упражнения: у тебя есть контекст, чтобы судить, о чём ошибка.

Не гонись за идеальными типами. Если тип занимает больше нескольких минут, возьми более простой и иди дальше. Ценность в тех девяноста процентах, которые даются легко.

На практике

Аннотируй параметры и возвращаемые типы, остальное оставь выводу.

Замени комбинации булевых значений объединением тех состояний, которые действительно существуют. И пусть компилятор скажет, какой случай ты забыл.

Поищи по проекту any и почини три худших. Каждый — дыра, через которую течёт всё, что ниже.

Проверь один ответ API на границе схемой и заметь, сколько защитного кода исчезает, когда форма гарантирована на входе.

Неделю намеренно пользуйся переименованием и поиском обращений. Это быстрейший способ почувствовать, что дают типы, и это меняет твою готовность править собственный код, что стоит больше, чем кажется.

Проверь себя

Закрой статью и ответь своими словами:

  1. Что происходит с типами на сборке и что из этого следует про защиту во время работы?
  2. Что стоит аннотировать, а что оставить выводу типов?
  3. Как объединение придаёт реальную силу правилу «сделай недопустимые состояния невыразимыми»?
  4. Что убирает строгая проверка на null?
  5. В чём разница между any и unknown и почему утверждение типа — не проверка?
  6. Почему объявление типа для ответа API не защищает и какие есть два честных варианта?
  7. Какова главная повседневная выгода и почему это довод не только про продуктивность, но и про обучение?

Коротко

  • Типы — это знание, которое у тебя уже есть о собственных функциях, записанное туда, где его кто-то проверяет.
  • TypeScript проверяется при сборке и стирается: ни стоимости во время работы, ни защиты во время работы.
  • Аннотируй параметры и возвращаемые типы, опиши формы данных один раз, остальное оставь выводу.
  • Объединения превращают шаблон со статусом в то, что обеспечивает компилятор, включая напоминание о забытом случае.
  • Строгая проверка на null убирает бо́льшую часть семейства «cannot read properties of undefined» — самой частой ошибки во фронтенде.
  • any выключает проверку для всего, что течёт дальше; unknown — честная версия, а утверждения типа — обещания, а не проверки.
  • Типы заканчиваются на границе: проверяй сетевые данные и хранилище во время выполнения либо знай, что допускаешь.
  • Настоящая выгода — миллисекундная петля обратной связи в редакторе: точное автодополнение, безопасные переименования, ошибки до сохранения.