Сетка: типы как документация, которая не умеет врать
У каждой написанной тобой функции уже есть тип — просто ты держишь его в голове, где его никто не проверяет и где он потихоньку перестаёт быть правдой.
Ты пишешь 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 на границе схемой и заметь, сколько защитного кода исчезает, когда форма гарантирована на входе.
Неделю намеренно пользуйся переименованием и поиском обращений. Это быстрейший способ почувствовать, что дают типы, и это меняет твою готовность править собственный код, что стоит больше, чем кажется.
Проверь себя
Закрой статью и ответь своими словами:
- Что происходит с типами на сборке и что из этого следует про защиту во время работы?
- Что стоит аннотировать, а что оставить выводу типов?
- Как объединение придаёт реальную силу правилу «сделай недопустимые состояния невыразимыми»?
- Что убирает строгая проверка на null?
- В чём разница между
anyиunknownи почему утверждение типа — не проверка? - Почему объявление типа для ответа API не защищает и какие есть два честных варианта?
- Какова главная повседневная выгода и почему это довод не только про продуктивность, но и про обучение?
Коротко
- Типы — это знание, которое у тебя уже есть о собственных функциях, записанное туда, где его кто-то проверяет.
- TypeScript проверяется при сборке и стирается: ни стоимости во время работы, ни защиты во время работы.
- Аннотируй параметры и возвращаемые типы, опиши формы данных один раз, остальное оставь выводу.
- Объединения превращают шаблон со статусом в то, что обеспечивает компилятор, включая напоминание о забытом случае.
- Строгая проверка на null убирает бо́льшую часть семейства «cannot read properties of undefined» — самой частой ошибки во фронтенде.
anyвыключает проверку для всего, что течёт дальше;unknown— честная версия, а утверждения типа — обещания, а не проверки.- Типы заканчиваются на границе: проверяй сетевые данные и хранилище во время выполнения либо знай, что допускаешь.
- Настоящая выгода — миллисекундная петля обратной связи в редакторе: точное автодополнение, безопасные переименования, ошибки до сохранения.