
Мысли впервые были оформлены в комментарии к посту Linkedin.
Это субъективное мнение, сложившееся на основе многолетнего опыта разработки. Я не претендую на истину в последней инстанции и скорее хочу сформулировать конфликт двух подходов к созданию UI, чем объявить один из них единственно верным.
Бегство из мира Java и строгой типизации с ООП
Мой бэкграунд — это Java и ООП. Строгие типы на уровне компиляции, множество абстракций и полиморфизм, достигаемый бесчисленным количеством интерфейсов, — всё это контрастировало с динамическим и слабо типизированным JS.
Я досконально изучил все боли этой парадигмы: тонны бесполезного бойлерплейта, раздутые кодовые базы, абстракции, которые лишь тормозят и разработку, и доставку.
В какой-то момент я даже увлёкся языком Scala, который удачно дополнял Java и решал проблему жёсткого разделения парадигм на уровне синтаксиса, привнося в код более простые и гибкие функциональные конструкции. Мартин Одерски долгое время тесно работал с Java и, зная все её боли, проделал блестящую работу, создав на этом фундаменте новый язык, вдохновлённый сразу несколькими парадигмами и такими языками, как Lisp.
Увы, Scala так и осталась нишевым языком в сфере обработки больших данных и параллельных вычислений, так и не вытеснив и не заменив Java.
По итогу, я сбежал от этого в мир фронтенда в поисках свободы, тотального полиморфизма и гибкости. И теперь эту свободу у меня забрал TypeScript под лозунгом: "Мы спасем мир от ошибок, связанных с типами!", навязывая статическую типизацию и всё ту же культуру чрезмерного усложнения, от которой я когда-то ушёл.
Я видел, как очень умные люди, в десятки раз умнее меня, спотыкались о дженерик внутри дженерика, который описывает другой дженерик и просили помощи разобраться у других. Помню даже, как один уважаемый инженер тогда сказал, глядя на этот код: "Что это за заклинание по вызову дьявола?"
За любой разработкой стоит человек, и его багаж знаний и опыта никуда не исчезает. Андерс Хейлсберг разрабатывал Object Pascal, Delphi и C#, а не JavaScript или Python. Было бы наивно считать, что такая профессиональная деформация направит его к пониманию силы и красоты динамических типов, а не к статической модели. И это не аргумент ad hominem, а факт: дизайнер языка долгие годы жил в совершенно другой парадигме, и этот багаж нельзя просто так выкинуть.
Конечно, TypeScript не является перенесённым во фронтенд C#: у него структурная типизация, вывод типов, объединения, пересечения и возможность постепенно внедрять проверки. Но базовое убеждение всё равно осталось прежним: программу желательно описать статической моделью до её выполнения. И именно перенос этого убеждения на JavaScript я считаю принципиальным выбором, а не нейтральным улучшением инструментария.
Утрата первоначальной философии JavaScript
Ни для кого не секрет, что перед Брендоном Айком стояла задача разработать скриптовый язык с низким порогом входа, понятный не только инженерам, но и дизайнерам. JavaScript создавался как язык для "непрограммистов" — дизайнеров и верстальщиков из мира бумажной типографики, которым нужно было добавить немного интерактивности на веб-страницы.
Уж не знаю, закладывал ли Брендон это все в дизайн языка, но эта "инаковость", этот легкий порог входа были его наибольшей силой. Язык должен был оставаться доступным и развиваться в своей парадигме, а не становиться жертвой "высоких инженеров", которые навязали ему привычные им паттерны из других экосистем. TypeScript систематически убивает эту легкость, превращая фронтенд из творческого процесса в рутину инженерных практик.
Типы не принадлежат UI
Типы полностью оправданы на уровне больших корпоративных бэкендов, работающих с БД, моделей и контрактов с сервером. Но на уровне представления, какой бы тип у вас ни был, данные в конечном счёте связываются с HTML и превращаются в текст, атрибут или визуальное состояние.
Это не буквальное утверждение о том, что DOM API не знает boolean, объектов или событий. Это философия слоя представления: типизированная модель передаёт данные в пластичный динамический шаблон, задача которого — выбрать структуру и отобразить их. TypeScript меняет саму парадигму: типизированными становятся не только модель и внешний контракт, но и props, локальное состояние, варианты компонентов и шаблон.
Я не тотально за то, чтобы писать код без строгой типизации. Типы определённо помогают организовать согласованность в коде, ловят баги ещё на стадии компиляции и полезны для публичных интерфейсов компонентов. Но из своего многолетнего опыта именно на фронтенде я понимаю, что нужен компромисс, потому что строгая типизация убивает кучу классных синтаксических конструкций слабой типизации, которые адепты TypeScript пометили как "запрещённые" или "нежелательные".
Современный UI — это не только финальный HTML: в нём есть ветвление состояний, формы, события и асинхронность. Ошибки в props и невозможные состояния существуют. Однако статическая модель TypeScript не всегда сохраняет эргономику динамических конструкций JavaScript, а цена их точного описания может превышать пользу проверки. Дженерик внутри дженерика часто появляется не из-за сложности задачи, а из-за попытки доказать компилятору корректность простого динамического приёма.
Мне гораздо ближе идея рантайм-валидаторов вроде Joi или Zod. На мой взгляд, это правильный путь, потому что они работают тогда, когда код выполняется, и проверяют реальные входящие данные, а не представление о них на этапе компиляции. И давайте не будем забывать, что TypeScript — это надстройка, слой поверх языка, который делает статические проверки, после чего они бесследно исчезают из финального бандла.
Мне возразят, что валидаторы и тесты не заменяют статические типы: первые защищают границы системы, а вторые проверяют использование уже валидированных значений внутри программы. Это так, но на практике две системы часто дублируют друг друга. Мы отдельно описываем interface User и отдельно — z.object({ ... }), обслуживаем две семантические модели и рискуем получить их рассогласование. z.infer устраняет синтаксическое дублирование, но не концептуальное: runtime-ограничения, coercion, defaults и transforms всё равно живут не в системе типов TypeScript.
Показателен путь Python — динамически, но сильно типизированного языка. В нём тоже существуют статические анализаторы, например mypy и Pyright, однако аннотации остаются доступны в рантайме. Поэтому одна модель Pydantic может одновременно служить валидатором, источником JSON Schema, документацией и основой для статического анализа. В TypeScript объявленный interface User после компиляции не существует, поэтому рантайм-модель приходится строить параллельно. Это не случайный недостаток библиотеки, а последствие фундаментального решения стирать типы.
На самом деле я очень часто спрашивал JS-команды и отдельных JavaScript разработчиков, как часто ошибки в их продакшенах были связаны с типами и является ли это заметной частью бэклога с багами. Мне отвечали, что такие случаи бывают, но редко становятся "узким горлышком". В моей практике комбинация валидатора с хорошими модульными и интеграционными тестами часто давала более выгодный баланс, нежели танцы с настройкой статического анализа и написания кучи бойлерплейта. Это эмпирическое наблюдение, а не универсальное доказательство.
tsc как чёрный ящик
JavaScript-код, который производит tsc или другой транспилятор, — это ещё один чёрный ящик. Точнее, проблема даже не в том, что результат невозможно посмотреть: его можно посмотреть, а во многих проектах tsc вообще запускается с --noEmit. Проблема в дополнительной модели программы, конфигурации и инструменте, которым мы отдаём часть контроля. Наличие Babel, минификатора и JIT браузера не оправдывает новый слой: количество абстракций растёт, и инженерно это не обязательно верный и целевой путь.
Нужно различать runtime-слой и сложность разработки. После стирания типов выполняется обычный JavaScript, поэтому TypeScript дешевле виртуальной машины или полноценной трансляции между разными runtime. Но разработчик всё равно платит медленными проверками, source maps, расхождениями между настройками IDE, checker и bundler, конфликтами деклараций и необходимостью понимать одновременно реальную семантику JavaScript и представление TypeScript о ней.
Это не буквально перевод Java в Ruby, как иногда хочется эмоционально описать происходящее. Но направление мысли похоже: между исходником и исполняемым кодом появляется посредник, корректность и стоимость которого команда принимает как данность.
Ухудшение Developer Experience (DX)
На мой взгляд, DX от TypeScript чаще проигрывает. Это постоянная возня с новыми конфигами и дополнительная настройка, которая увеличивает сложность фронтенд-операций. И не стоит забывать про бесчисленные пакеты @types, которые сжирают мои CPU-циклы, пока IDE всё это индексирует. Навигация, автодополнение и безопасный рефакторинг — реальные преимущества, особенно в большой изменяемой кодовой базе. Но они не бесплатны, и превращать их наличие в универсальный аргумент в пользу TypeScript — плохая инженерия.

Исторический контекст и навязывание
Возможно, внутри самой спецификации ECMAScript произошла бы революция с типами и появились бы механики самого языка, а не стираемая надстройка над ним.
И последнее, но не менее важное — исторический контекст взлёта TypeScript. Нельзя доказать, что его популярность не была органической: очевидно, инструмент решил реальные проблемы огромного количества разработчиков. Но она точно не была исключительно органической. Его активно продвигали крупные tech-корпорации вроде Microsoft и Google, формируя документацию, инструментарий и ожидания рынка.
Помните документацию по Angular 2 beta? В ней примеры были почти исключительно на Dart и TypeScript, с едва заметными намёками на чистый JS. Корпорации естественным образом продвигают собственный инструментарий и пытаются оправдать инвестиции в его разработку. Слава богу, Dart не зашёл во фронтенде. Такое внедрение отталкивает на человеческом уровне. На тот момент уже были другие направления — propTypes, Flow и JSDoc, — но TypeScript постепенно вытеснил их, обрекая альтернативы на стагнацию.
Возможно, внутри самой спецификации ECMAScript произошла бы революция с типами и появились бы механики самого языка, а не надстройки над ним. Мы могли бы получить аннотации, доступные в рантайме, и построить вокруг них валидаторы по модели Python и Pydantic.
Утверждать, что без TypeScript это обязательно произошло бы, нельзя: история не имеет контрольной группы. JavaScript мог остаться с JSDoc и набором несовместимых валидаторов. Но TypeScript очень рано зафиксировал направление развития и резко снизил вероятность альтернативного пути. Библиотеки публикуют .d.ts, IDE ориентируются на TypeScript, а любое новое предложение вынуждено учитывать совместимость с его огромной экосистемой.
Даже остающееся на Stage 1 предложение TC39 Type Annotations описывает подход "types as comments": движок должен игнорировать аннотации, оставляя проверку внешним инструментам. Получается, влияние TypeScript заметно даже в предполагаемом нативном развитии JavaScript: обсуждается стандартизация пространства для стираемой типизации, а не единая runtime-модель. Доминирующее решение не просто победило конкурентов — оно изменило пространство решений, которые индустрия теперь способна всерьёз рассматривать.
TypeScript, по моему мнению, стал большой ошибкой не потому, что статические типы бесполезны. Он стал ошибкой потому, что локальное решение проблемы редактора и рефакторинга превратилось в де-факто стандарт, распространивший одну инженерную парадигму на весь фронтенд и сузивший пространство для развития самого JavaScript.
Возможно, частью решения было бы просто лучше изучить JavaScript (как всегда говорит Кайл Симпсон), а не автоматически накладывать на него ещё один слой абстракции, противоречащий изначальному духу языка. Это не означает запретить TypeScript: для большой изменяемой кодовой базы он может окупаться навигацией и безопасным рефакторингом. Но выбор должен следовать из цены конкретной ошибки и устройства конкретного продукта, а не из де-факто стандарта. Когда Симпсон высказал своё мнение по этому поводу в соцсетях, содержательный разговор быстро утонул в реакции токсичных поклонников TypeScript и типов.
Помните принцип YAGNI и KISS? Нужно задуматься, мы добавляем сложность потому, что это решает реальные проблемы, или просто следуем тренду?
P.S. Самая яркая ирония во всей этой истории — сам TypeScript стал настолько медленным и сложным, что сообщество начало массово переписывать инструменты его экосистемы на Rust и Go.
Мы получили абсурдную ситуацию: чтобы комфортно использовать TypeScript во фронтенде, нам теперь нужны компиляторы, написанные на других языках, — SWC (Rust), esbuild (Go), Oxc (Rust). На практике они не обязательно выстраиваются в последовательную цепочку: один быстрый инструмент может одновременно стереть типы, собрать и минифицировать код. Но концептуальный стек никуда не исчезает: модель TypeScript → проверяющий инструмент → транспилятор или бандлер → JavaScript.
Выбор Rust или Go сам по себе не доказывает ошибочность архитектуры TypeScript — это лишь выбор более производительной реализации. Pydantic v2, который я привёл как более цельную модель, тоже использует Rust в своём ядре, поэтому было бы нечестно делать язык реализации самостоятельным аргументом. Разница не в Rust, а в количестве обслуживаемых моделей и границ между ними.
Но масштаб оптимизации хорошо показывает цену уже принятого решения. Вместо сокращения количества концепций мы удешевляем накопленную сложность ещё одним инструментом. Проблемного посредника приходится оптимизировать с помощью половины языков программирования, а простой инструмент превращается в инженерный стек, который требует постоянной настройки и поддержки.
Это напоминает историю с Babel: сначала простой транспилятор, потом монстр с кучей плагинов, после чего сообщество ищет альтернативы. Переписывание инструментов уменьшает стоимость выбранной архитектуры, но ещё не доказывает, что сама архитектура была целевой.