Содержание
  1. Матрица A/B: два проекта, условное решение
  2. Полный order-js.js и полный order-ts.ts
  3. Отдельная ошибка: total как строка
  4. Внешний JSON: runtime-проверка, as не валидирует
  5. Две проверки: компиляция и внешние входы
  6. Как выбрать: порядок решения
  7. Как читать этот разбор и что делать при расхождении
  8. Частые вопросы
Подборки и сравнения

TypeScript или JavaScript: что выбрать проекту

5 ноября 2026 · 10 минут чтения

TypeScript или JavaScript: что выбрать проекту — вопрос решается двумя свойствами: есть ли у команды сборка и как часто меняются контракты. Разовый виджет на статике живёт на JavaScript. Командный сервис с CI, рефакторингами и JSON-API получает TypeScript как первый кандидат для внутренних контрактов.

Матрица A/B: два проекта, условное решение

Сравнение идёт по потребностям. Ниже два проекта с разными вводными.

Критерий Проект A: разовый виджет Проект B: командный сервис
Владелец кода один человек команда
Сборка отсутствует CI собирает
Данные статический объект JSON из API
Частота правок редкая частые рефакторинги
Выбор обычный JS TS для контрактов
Дополнительно JSDoc/checkJs по желанию runtime-валидатор для API

Логика такая. В проекте A нет шага сборки, объект статический, правки редкие — аннотации не окупают настройку. В проекте B сборка уже есть, контракты меняются часто, а JSON приходит извне — статические типы дают раннюю диагностику при рефакторинге.

Официальный handbook TypeScript описывает язык как надмножество JavaScript: синтаксис JS остаётся допустимым TS, а типы добавляют правила обращения со значениями. Там же сказано, что TypeScript — статический проверяющий типов: он ищет ошибки до выполнения, опираясь на виды значений. Это ровно та функция, которая нужна проекту B при частых правках.

Для проекта A полезна отдельная статическая проверка без перехода на TS. Официальная страница опции checkJs сообщает: при включённой опции ошибки выводятся в JavaScript-файлах, а строка // @ts-check в начале файла включает ту же проверку. Опция allowJs, по официальной документации, разрешает импорт JS-файлов внутрь проекта и служит способом постепенно добавлять TypeScript рядом с существующим JavaScript.

Матрица отвечает на вопрос выбора, но не показывает, как выглядит проверка. Дальше — полные файлы обоих проектов.

Полный order-js.js и полный order-ts.ts

Проект A: файл с JSDoc-типом и включённой проверкой.

JS4 строки
// @ts-check
/** @typedef {{id: string, total: number}} Order */
/** @type {Order} */
const order = {id: "Q-71", total: 240};

Проект B: модуль с типом Order и функцией разбора внешнего значения.

TS11 строк
export type Order = {id: string; total: number};
export function parseOrder(raw: unknown): Order {
  if (typeof raw !== "object" || raw === null ||
      !("id" in raw) || !("total" in raw) ||
      typeof raw.id !== "string" || typeof raw.total !== "number" ||
      !Number.isFinite(raw.total)) {
    throw new TypeError("Invalid Order");
  }
  return {id: raw.id, total: raw.total};
}
const order: Order = {id: "Q-71", total: 240};

Обе конструкции — учебные эталоны. Значения "Q-71" и 240 взяты как условный вход.

В файле проекта B видна ключевая деталь: параметр raw объявлен как unknown. Официальная документация по сужению типов описывает, что TypeScript следует путям выполнения и уточняет тип значения в конкретной позиции; проверки typeof, оператор in и сравнения с литералами работают как type guards. В parseOrder эти проверки последовательно отсекают всё, что не похоже на Order, и только после них возвращается объект нужной формы.

Файл проекта A опирается на тот же механизм проверки, но тип описан в JSDoc. Официальная документация checkJs приводит пример: вызов parseFloat с числом даёт ошибку «Argument of type ‘number’ is not assignable to parameter of type ‘string’», когда проверка включена. То есть JSDoc-аннотации дают статическую диагностику в JS-файле без переименования в .ts.

Разница между файлами — в точке контроля. В A тип живёт в комментарии и проверяется отдельным инструментом. В B тип экспортируется как часть модуля, и его видят все импортирующие файлы.

Отдельная ошибка: total как строка

Намеренно ошибочный фрагмент. Он не входит в рабочие файлы выше и существует отдельно.

TS
const bad: Order = {id: "Q-71", total: "240"};

Здесь total получает строку "240" там, где тип Order требует число. Ожидаемый результат — ошибка типа о несовместимости string и number. Этот фрагмент не является частью проходящего файла order-ts.ts и не должен попадать в него при копировании.

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

Практический вывод для проекта B: строгий режим повышает шанс поймать несоответствие вроде total: "240" на этапе проверки. Цена — возможные новые ошибки при обновлении компилятора, которые придётся разбирать.

Внешний JSON: runtime-проверка, as не валидирует

Граница с внешними данными — место, где статические типы заканчиваются. JSON из API приходит как текст и разбирается в значение, форма которого заранее не гарантирована. В проекте B это отражает сигнатура parseOrder(raw: unknown): тип unknown фиксирует, что до проверки о значении не известно ничего.

Функция проверяет последовательно: значение является объектом и не равно null; в нём есть свойства id и total; id — строка; total — число; total конечно. При несоответствии выбрасывается TypeError("Invalid Order"). При соответствии возвращается объект с полями нужных типов.

Учебные входы и ожидаемые результаты:

Вход Результат
parseOrder({id:"Q-71",total:240}) возвращает эти данные
parseOrder({id:"Q-71",total:"240"}) бросает TypeError

Оператор as Order данные не проверяет и эту функцию не заменяет. Утверждение типа сообщает компилятору, каким тип считается, и на разбор внешнего значения не влияет. Официальная документация по сужению типов показывает, что не-null утверждение (!) приходится применять там, где проверка не выполнена, и такие утверждения становятся источником ошибок при перемещении кода. Для границы API надёжнее явная функция разбора.

Порядок действий для проекта B: объявить тип контракта; написать функцию разбора с параметром unknown; проверять каждое обязательное поле; выбрасывать ошибку при несоответствии; вызывать разбор сразу после получения JSON. Типы описывают ожидаемую форму, а проверка подтверждает её на конкретных данных.

Две проверки: компиляция и внешние входы

Проверка первая — статическая. Учебный конфиг для обоих файлов:

JSON
{"compilerOptions":{"strict":true,"noEmit":true,"target":"ES2022","allowJs":true,"checkJs":true},"include":["order-ts.ts","order-js.js"]}

Команда в проекте с доступным компилятором TypeScript: tsc -p tsconfig.json. Ожидание: рабочие файлы order-ts.ts и order-js.js проходят проверку, отдельная строка const bad: Order = {id: "Q-71", total: "240"}; даёт ошибку типа. Конфиг учебный; редакция его не запускала.

Проверка вторая — на внешних входах. Ожидание: parseOrder({id:"Q-71",total:240}) возвращает эти данные, parseOrder({id:"Q-71",total:"240"}) бросает TypeError. Эти результаты — учебные эталоны.

Что даёт пара проверок. Статическая ловит несоответствия внутри кода до запуска. Runtime-проверка закрывает границу, куда статический анализ не достаёт. Одна не заменяет другую: конфиг проверяет форму кода, функция проверяет конкретные данные.

Отдельно про запуск. Аннотации TypeScript стираются или трансформируются инструментами сборки; конкретный способ запуска зависит от среды. Современные среды выполнения могут читать .ts напрямую, поэтому утверждение о единственном способе запуска было бы неверным. Официальный handbook описывает стирание типов: после проверки компилятор удаляет типы, и полученный JavaScript не содержит информации о типах. Там же сказано, что TypeScript сохраняет поведение JavaScript во время выполнения и не добавляет собственных runtime-библиотек — программы используют ту же стандартную библиотеку, что и JavaScript.

Как выбрать: порядок решения

Первый шаг — проверить наличие сборки. Если шага сборки в проекте нет и добавлять его ради типов не планируется, разумная отправная точка — JavaScript с отдельной проверкой через JSDoc и checkJs. Официальная документация checkJs описывает строку // @ts-check как способ включить проверку в конкретном файле.

Второй шаг — оценить частоту изменений контрактов. Частые рефакторинги и несколько участников повышают ценность статической проверки: несоответствие обнаруживается до запуска. Официальная документация strict описывает строгий режим как набор проверок с более сильными гарантиями корректности.

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

Четвёртый шаг — учесть стоимость перехода. Опция allowJs, по официальной документации, позволяет JS-файлам жить рядом с TypeScript и служит способом постепенного внедрения. Переход можно вести файл за файлом.

Пятый шаг — зафиксировать решение в матрице проекта. Для A достаточно JS и, при желании, отдельной проверки. Для B — TypeScript для внутренних контрактов и отдельный валидатор для API.

Как читать этот разбор и что делать при расхождении

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

Возможная ошибка при чтении — перенести вывод проекта B на проект A. Строгий режим и функция разбора полезны там, где контракты живут долго и меняются часто. Для разового виджета сначала оцените пользу отдельной проверки типов и сопровождения конфига. Обратная ошибка тоже встречается: взять из проекта A привычку обходиться без проверки границы и применить её к сервису, который читает JSON из сети. Тогда несоответствие формы проявится уже во время работы, когда данные пришли от внешнего владельца.

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

Действие читателя после разбора — прогнать оба своих случая через матрицу и записать решение в одном месте: для каждого проекта указать, есть ли сборка, кто владелец кода, нужна ли runtime-проверка. Затем скопировать подходящий файл, заменить условные значения "Q-71" и 240 на свои и убедиться, что проверка из последнего раздела даёт ожидаемый результат на вашем наборе данных. Если результат расходится с ожиданием, сначала проверьте, не попала ли ошибочная строка с total в виде строки в рабочий файл, и не заменена ли функция разбора утверждением типа. Проверьте оба места при переносе примера в свой проект.

Частые вопросы

TypeScript или JavaScript — что выбрать для нового проекта?

Решение зависит от сборки и частоты правок. Проект без сборки с редкими изменениями обходится JavaScript. Проект с CI, командой и частыми рефакторингами получает TypeScript для внутренних контрактов. Внешние данные в обоих случаях требуют runtime-проверки.

TypeScript работает медленнее JavaScript?

Официальный handbook описывает, что TypeScript сохраняет поведение JavaScript во время выполнения и не добавляет собственных runtime-библиотек. Программы используют ту же стандартную библиотеку. Статическая проверка происходит до запуска и на скорость выполнения кода не влияет.

Можно ли запускать файлы .ts напрямую?

Способ запуска зависит от среды. Аннотации TypeScript стираются или трансформируются инструментами сборки. Современные среды выполнения могут читать .ts напрямую, поэтому единого правила для всех сред нет.

Заменяет ли as Order проверку внешнего JSON?

Нет. Утверждение типа сообщает компилятору, каким тип считается, и данные не проверяет. Для разбора внешнего значения нужна функция с параметром unknown, которая проверяет поля и выбрасывает ошибку при несоответствии.

Что даёт флаг strict в tsconfig?

Официальная документация описывает strict как включение широкого набора проверок с более сильными гарантиями корректности. Отдельные проверки семейства strict можно отключать. Будущие версии TypeScript могут добавить более строгие проверки под этим флагом.

Читайте также

3 материала