Основы TypeScript
TypeScript — это статически типизированное надмножество JavaScript от Microsoft. Любой валидный файл JavaScript одновременно является валидным TypeScript, но сверху добавляется слой необязательных аннотаций типов, которые проверяются на этапе компиляции. Момент, который упускают новички: этот слой существует только до запуска. Ни браузер, ни Node не понимают TypeScript — компилятор tsc транспилирует код в обычный JavaScript и полностью стирает типы. Ошибка типа — это ошибка компиляции, а не рантайма, и никакой рантайм-гарантии аннотация не даёт.
Из этого одного факта — «типы живут только при компиляции» — вытекает почти всё остальное. Компилятор редко требует писать типы вручную: он выводит их, и делает это в двух направлениях. Снизу вверх он читает значение (let n = 10 → number); сверху вниз — контекстно — берёт тип из позиции, в которой стоит выражение, поэтому параметр колбэка не нужно аннотировать, а та же стрелка, вынесенная в «голую» const, внезапно падает с implicit any. При выводе из литерала включается расширение: изменяемая привязка получает базовый тип (string, number), неизменяемая — точный литерал; отсюда и коронный вопрос темы про let против const, и классический баг с расширенным дискриминантом, который ломает размеченное объединение. Наконец, let/const блочные, а var — функциональный и поднимается, при этом const фиксирует лишь ссылку и не делает объект неизменяемым. Слои ниже разбирают эти механизмы по порядку.
Карта темы
- Статическая типизация — что значит «проверка типов на этапе компиляции», структурная типизация по форме и почему в рантайме проверок уже нет.
- Транспиляция — как
tscпревращает TypeScript в обычный JavaScript, стирает типы и что из синтаксиса всё же порождает рантайм-код. - Аннотации типов — синтаксис
: Typeна переменных, параметрах и возвращаемых значениях, его необязательность и чемanyотличается от опущенного типа. - Вывод типов — как компилятор определяет тип «снизу вверх» из значения, тела функции и наиболее общего типа, и почему вывод — это не
any. - Контекстная типизация — второе направление вывода, «сверху вниз»: тип параметра колбэка приходит из сигнатуры, а вынесенная функция теряет контекст.
- Расширение типов — почему
letрасширяет литерал до базового типа, свойства объекта расширяются всегда, и как это ломает размеченное объединение. - Объявление переменных —
let,const,var, блочная против функциональной области видимости, hoisting и временная мёртвая зона (TDZ). - const и неизменяемость — почему
constфиксирует ссылку, а не содержимое, и как добиться настоящей неизменяемости черезreadonly,as const,Object.freeze.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
| Думать, что типы проверяются в рантайме | Аннотации стираются tsc; в рантайме нет информации о типах — движок исполняет чистый JavaScript без проверок |
Ожидать, что браузер или Node запустят .ts напрямую | Код не выполнится без предварительной транспиляции через tsc (или ts-node / esbuild) |
| Считать, что стирается вообще всё | interface, type и аннотации исчезают, но enum и параметры-свойства конструктора порождают реальный JS-код |
| Считать TypeScript номинально типизированным | Совместимость — по форме (структурно): объект с нужными полями подходит без явного implements |
Считать аннотацию : Type обязательной | Лишний шум — опущенный тип надёжно выводится из инициализатора; аннотируйте границы, не внутренности |
Считать, что отсутствие аннотации даёт any | Вывод даёт конкретный тип; any появляется, лишь когда выводить не из чего (нетипизированный параметр при noImplicitAny) |
| Аннотировать параметр вынесенного колбэка не нужным | Без контекста параметр (x) => … в «голой» const становится implicit any — контекст был только в позиции аргумента |
Ждать, что arr.map(x => x.length) и const f = x => x.length ведут себя одинаково | Первый типизируется контекстом из сигнатуры map, второй — ошибка: у выражения нет целевой позиции |
Путать const x = 10 и let x = 10 при выводе | const выводит литеральный тип 10, let расширяет до number — разные типы там, где ждали одинаковый |
Думать, что под const свойства объекта сохраняют литеральный тип | Свойства расширяются всегда (они мутабельны): const o = { kind: 'a' } даёт { kind: string } |
| Собирать размеченное объединение из обычных объектов | Дискриминант расширяется до string и объект не подходит под ветку union — лечится as const или аннотацией целевым типом |
Считать let/const функциональными и поднимаемыми как var | Неверная модель области видимости; обращение в «мёртвой зоне» (TDZ) даёт ReferenceError |
Ждать, что var i в цикле создаёт по переменной на итерацию | Один общий var i захватывается всеми колбэками — печатает 3 3 3; let даёт свежую привязку и 0 1 2 |
Думать, что const делает объект неизменяемым | push в const-массив и запись свойств проходят молча — фиксируется только ссылка |
Считать Object.freeze глубокой заморозкой | Заморожен только верхний уровень; вложенные объекты остаются изменяемыми |
Значение для собеседований
TypeScript-собеседование почти всегда открывается этими вопросами — и проверяют не заученные определения, а модель языка. Кандидат, который объясняет tsc через «проверяет и стирает типы, оставляя обычный JavaScript», а вывод — через «два направления: из значения и из контекста», сразу выделяется на фоне «ну, добавляет типы».
Что обычно проверяют:
- Чем TypeScript отличается от JavaScript, почему любой валидный JS является валидным TS и что типизация структурная, а не номинальная.
- Что типы проверяются на этапе компиляции и стираются — рантайм-гарантий нет; и что
enum/ параметры-свойства всё же оставляют код. - Как работает вывод «снизу вверх» (значение, тело функции, наиболее общий тип) и почему он не равен
any. - Что такое контекстная типизация и почему параметр колбэка не нужно аннотировать, а вынесенная функция теряет тип.
- Почему
let x = 'a'— этоstring, аconst x = 'a'—'a'; что свойства объекта расширяются всегда и как это ломает размеченное объединение. - Разницу
let/const/var: блочная против функциональной области, hoisting и TDZ. - Что
constфиксирует привязку, а не значение, и как добиться настоящей неизменяемости (readonly,as const, поверхностностьObject.freeze).
Типичный неверный ответ: «TypeScript — это язык, который браузер исполняет с проверкой типов». Он открывает разговор о том, что TS не запускается сам, типы существуют только до транспиляции и в рантайме от них не остаётся и следа. Второй частый провал — «раз объявлено через const, то и литеральный тип свойства сохранится»: на деле привязка зафиксирована, а свойства мутабельны и расширяются, из-за чего kind: 'a' становится string и объект перестаёт подходить под размеченное объединение.