Перетяжка, Карта дня
Перетяжка, Карта дня
Перетяжка, Карта дня

Эпоха TypeScript: почему JavaScript без строгой типизации умирает? Или нет?

Представьте: вы запускаете новый функционал на продакшен, всё кажется отлично — но через час приходит сообщение от коллег: «Всё сломалось». Знакомо? Для JavaScript-разработчиков это обычная ситуация. Вместе разберёмся, почему так происходит и как TypeScript может спасти от бессонных ночей с дебаггером.

467 открытий3К показов
Эпоха TypeScript: почему JavaScript без строгой типизации умирает? Или нет?

JavaScript долгое время был королем веб-разработки, гибким и всепрощающим. Но с ростом сложности проектов его слабая типизация стала больше проблемой, чем преимуществом. TypeScript — строгий, надежный, предсказуемый — набирает популярность, обещая избавить разработчиков от множества ошибок. Так умирает ли JavaScript без типизации, или он по-прежнему незаменим? Давайте разберемся.

Проблема динамической типизации

Как мы обычно объявляем переменные в JavaScript? Просто пишем let или const, даём имя и присваиваем значение:

			let userName = "Анна";
const userAge = 25;
		

Никаких указаний типа. JavaScript не требует от нас заранее объявлять, что userName — это строка (string), а userAge — число (int). И в этом кроется как сила, так и слабость языка.

В JavaScript переменная может хранить любое значение: строку, число, объект, функцию — что угодно. Более того, мы можем менять тип переменной прямо в процессе выполнения:

			let data = 42;        // data -- число
data = "Привет";      // а теперь строка
data = { id: 100 };   // теперь объект
data = () => "wow";   // а теперь функция
		

Это и есть динамическая типизация — система, когда тип переменной определяется не на этапе написания кода, а во время его выполнения, и может свободно меняться.

Удобно, да? Не надо прописывать int или string, среда сама определит, какой тип у переменной. Только вот иногда такой подход приводит к куче ошибок, особенно когда мы имеем дело с большим проектом.

Разберём на примере функции, которая рассчитывает итоговую стоимость товаров:

			// Пользователь добавил два товара в корзину, цены пришли из формы
const price1 = "500"; // цена первого товара (строка)
const price2 = "300"; // цена второго товара (строка)

const totalPrice = price1 + price2; // Ожидаем сумму 800 рублей
console.log(totalPrice); // "500300" -- что-то пошло не так!
		

В этом примере мы получим 500300 вместо 800. Дело в том, что мы данные здесь в виде строки, а не числа. При этом во время выполнения JS решил, что мы хотим провести конкатенацию строк, и склеил числа.

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

В статье «Python Versus C++: An Analysis of Student Struggle on Small Coding Exercises in Introductory Programming Courses» проанализировали успехи студентов в решении задач по программированию на Python и C++. Студенты, которые работали с Python, чаще сталкивались с трудностями при решении задач (23% против 13% у C++) и это при том, что задачи были одинаковые. Python — язык с такой же динамической типизацией данных, как и JS. Большинство трудностей студентов были связаны с ошибками типизации. Вывод: динамическая типизация увеличивает количество ошибок.

Критичность ошибки с типами в крупных проектах

Теперь представьте, что мы работаем над системой рекомендаций в каком-нибудь крупном сервисе по типу Netflix. У нас десятки тысяч строк кода, тысячи переменных, и нам непонятно, какого они типа.

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

А если это банковское приложение и команда прохлопала ошибку с типизацией? Ведь IDE её не подсветит, да и отладка может проигнорировать проблему с типами. Тогда из-за обидного бага компания каждую минуту будет терять бешеные деньги.

Инструменты JS для борьбы с ошибками типов и их недостатки

Не поймите неправильно, динамическая типизация — это удобно. Мы можем быстро разрабатывать прототипы, небольшие проекты и функции. Но в крупных проектах, таких как системы e-commerce или банковские приложения, где кодовая база превышает десятки тысяч строк, она может стать источником скрытых ошибок.

В JS есть инструменты, которые нивелируют эту проблему, но они тоже со своими нюансами:

JSDoc — аннотации типов прямо в комментариях, которые подсказывают IDE (например, VS Code) и разработчикам, что ожидается в коде:

			/** @type {{ id: number, name: string }} */
const user = { id: 1, name: "Анна" };
		

Плюс: даёт автодополнение и базовую проверку типов в редакторе. Минус: это не часть языка, а просто подсказки — JS сам по себе их не проверяет на этапе выполнения или сборки.

'use strict' — включает строгий режим, который ловит некоторые ошибки (например, использование необъявленных переменных):

			'use strict';
foo = 42; // ReferenceError: foo is not defined
		

Плюс: убирает часть «опасных» особенностей JS. Минус: не влияет на типизацию напрямую, а только на синтаксис и поведение.

Ручная проверка в applyDiscount. Мы можем добавлять свои проверки типов:

			function applyDiscount(price, discount) {
  if (typeof price !== 'number' || typeof discount !== 'number') {
    throw new Error('Аргументы должны быть числами');
  }
  return price - (price * discount / 100);
}
		

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

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

Что такое TypeScript и как он решает проблему типизации

Строгая статическая типизация

TypeScript — язык программирования от Microsoft со строгой статической типизацией. Это значит, что перед объявлением переменной мы заранее определяем тип данных. Например, вот так выглядит переменная строки в JS и TS:

			// В JavaScript (JS)
let jsString = "Привет, мир!";

// В TypeScript (TS)
let tsString: string = "Привет, мир!";
		

Мы добавляем к переменной tsString тип данных — string, тем самым, явно указываем, что наша переменная — это строка. Благодаря этому среда понимает тип данных до выполнения кода и распознаёт больше потенциальных ошибок.

При этом код на TypeScript компилируется в JavaScript. Так у нас сохраняются преимущества JS, но со статической типизацией и минимумом ошибок.

Более удобная работа в IDE

Компилятор TypeScript способен находить больше ошибок, чем JavaScript. Если среда разработки настроена корректно, то он будет находить ошибки на лету, включая проблемы с типами.

Рассмотрим 2 примера на JS и TS:

			// JavaScript без типизации
function add(a, b) {
  return a + b;
}
console.log(add(5, "3")); // "53" вместо 8

// TypeScript с типизацией
function add(a: number, b: number): number {
  return a + b;
}
// add(5, "3") вызовет ошибку при компиляции
		

В выводе с JS после отладки мы получим 53, хотя должно быть 8. Из-за ошибки с типизацией переменной b, среда, скорее всего, не подсветит эту проблему, консоль отладки тоже явно на неё не укажет.

В пример с TS мы увидим ошибку ещё до отладки, сразу после ввода кода:

Эпоха TypeScript: почему JavaScript без строгой типизации умирает? Или нет? 1
Подсветка ошибок в TypeScript и JavaScript

Но даже если по каким-то причинам IDE её не подсветит, мы обнаружим проблему при отладке:

Эпоха TypeScript: почему JavaScript без строгой типизации умирает? Или нет? 2
Вывод ошибки в отладке TypeScript

Статическая типизация — одна из сильных сторон TypeScript, которая упрощает поддержку проектов после их выпуска.

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

Лучшее понимание кода разработчиками

Также статическая типизация в TypeScript помогает лучше понять код. Мы можем видеть:

  • Какие параметры принимает функция и какого они типа.
  • Что функция возвращает.
  • Какие свойства имеет объект.
  • Какие значения может принимать переменная.

Это удобно при работе с большими проектами, где над одним кодом работают крупные команды.

TypeScript позволяет писать поддерживаемый и самодокументированный код, если команда разработки будет правильно использовать этот язык — не злоупотреблять any типом, создавать понятные интерфейсы, писать сигнатуру методов и их возвращаемый тип и т. д. В JavaScript это сделать значительно труднее, если не невозможно. Но эти вещи упрощают поддержку и увеличивают скорость отладки приложения.

Сложно представить большой и сложный проект без TypeScript — сегодня это стандарт. Он помогает избежать простых, но очень коварных ошибок, например, передача неправильного типа данных в метод — ошибку увидите сразу. В целом, в таких вещах можно отметить быстрый цикл обратной связи, так как компилятор сразу сообщит о проблеме, и код, который мог бы работать неправильно, не дойдёт до продакшена.
Александр МиранковВедущий программист (компания под NDA)
Сначала кажется, что TypeScript увеличивает время разработки и неспроста: приходится писать дополнительный код под описание типов, интерфейсов и так далее. Но программирование на TypeScript — это игра вдолгую: в дальнейшем все преимущества раскрываются. Некоторые ошибки без TypeScript неочевидны, и могут обнаружиться уже на этапе продакшена в флоу с реальным пользователем, или в лучшем случае на этапе тестирования. 
Андрей РикSenior fullstack-разработчик
На одном проекте в моей практике было стандартное правило, которое принимают разработчики, если серьезно относятся к типизации на Typescript. Это запрет на использование типа any. При этом, благодаря линтеру на этапе сборки коммита типа any в коде не было, приходилось все подробно типизировать. В процессе приходили новые разработчики и видели, что мы используем Typescript и все подробно описано, поэтому вопросов по коду у них почти не было.
Константин ЛёвушкинРуководитель отдела фронтенд-разработки компании SimbirSoft
Одним из ярких примеров стало внедрение TypeScript в разработку нашего фронтенда. Ранее в JavaScript у нас часто возникали проблемы, связанные с неправильными типами данных при передаче между модулями, особенно когда один разработчик писал код, а другой его использовал. В TypeScript строгая типизация и контракты между модулями помогли нам избежать многих таких ошибок. Еще один пример — API-интеграции. В JavaScript легко передать не тот объект или пропустить обязательное поле, что приводит к неожиданным багам в продакшене. С TypeScript мы смогли использовать интерфейсы и автоматические проверки.
Владислав СкворцовТоп-менеджер финансово технологической компании Нумизмат

Совместимость TypeScript с экосистемой JS

Многие популярные библиотеки и фреймворки поддерживают TypeScript. Например, можно смело кодить в React, Angular, Vue. Любой валидный JS код, в большинстве случаев, валиден и для TypeScript. Достигается это за счёт следующих «фишек»:

Тип any. Это универсальный тип данных, он отключает проверку типов в TypeScript. С одной стороны, этот подход немного неудобный, так как лишает разработчиков преимуществ статической типизации. С другой стороны, бывают случаи, когда это необходимо.

			async function fetchData(url: string): Promise<any> {
  return (await fetch(url)).json();
}
const data = await fetchData("api.example.com");
console.log(data.user.name); // TS молчит, но может упасть в рантайме
		

В этом примере функция берёт данные с сайта (API) и говорит TypeScript: «Не проверяй, что внутри, я сам разберусь с помощью any». Потом мы пишем data.user.name, и TS не ругается, даже если там нет user или name.

С одной стороны, any — это «костыль», который сводит на нет смысл TS, но с другой — он бывает полезен, например, при работе с плохо типизированными API.

Пакеты @types. Допустим, нам надо импортировать JS библиотеку в TypeScript код. Но эта библиотека не поддерживает TypeScript из коробки. Вариант с типом any нам не подходит, так как мы можем получить много ошибок. В таких случаях подойдёт @types.

Это обычные npm-пакеты из DefinitelyTyped. С их помощью мы получаем доступ к репозиторию, в котором хранятся файлы типов (.d.ts). В них прописаны типы данных для объектов, переменных и иных структур JS библиотек. С помощью этих файлов наш TS код сможет «переварить» JavaScript без ущерба для типизации.

На февраль 2025 года в DefinitelyTyped доступны типы для более чем 8 тыс. библиотек, что покрывает подавляющее большинство популярных npm-пакетов.

Пример установки пакета для библиотеки Lodash:

			npm install --save-dev @types/lodash
		

Файлы типов (.d.ts). Если мы используем какую-то старую JS библиотеку, она не поддерживает TypeScript и для неё нет файлов типов (.d.ts), мы можем прописать их вручную. Пример:

			declare module 'old-lib' {
  export function doMagic(input: string): number;
}

// main.ts
import { doMagic } from 'old-lib';
const result = doMagic("test"); // TS знает, что это number
		

Мы сделали файл .d.ts, чтобы рассказать TypeScript, что старая библиотека old-lib имеет функцию doMagic, которая берёт строку и возвращает число. Теперь, когда мы пишем doMagic('test'), TS понимает, что результат — это число, и не даёт нам случайно использовать его как строку.

Файлы типов (.d.ts) не добавляют исполняемый код, а лишь описывают структуру библиотеки для компилятора TypeScript. Они как переводчик с русского на английский, который подсказывает правильные артикли.

TypeScript создавался с прицелом на полную совместимость с JavaScript, так что серьёзных проблем обычно не бывает. За последние пару лет я сталкивался с трудностями только в тех случаях, когда использовались старые библиотеки без типизации. Например, однажды пришлось подключить устаревший пакет, у которого не было деклараций типов. Мы решили это, написав минимальный `.d.ts` файл вручную — заняло полчаса. Для современных и популярных библиотек таких проблем не бывает — как правило, у всех есть поддержка TypeScript из коробки.
Алексей КаньковSenior Backend Developer в компании Revizto

Недостатки TypeScript

Замедляет разработку

Когда мы пишем код на TypeScript, то вынуждены явно прописывать типы и исправлять большее количество ошибок, чем при разработке на чистом JS. Это замедляет процесс разработки.

Также не у всех JS библиотек есть встроенные типы TypeScript, иногда их приходится прописывать вручную, поэтому мы тратим время.

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

Избыточен для небольших проектов

Преимущества TypeScript актуальны для крупных продуктов, где работает команда разработчиков и много запутанного кода. Если мы пишем какой-то свой небольшой проект, например, одностраничный сайт, то у нас будет не так много объектов, классов, переменных и сложных структур. Можно написать это всё на TypeScript, чтобы упростить поддержку, но это замедлит разработку, плюс здесь можно обойтись и стандартными инструментами JS.

Сложная кривая обучения для новичков

TypeScript хоть и надстройка над JavaScript, но у него другой синтаксис. Здесь мы сталкиваемся со строгой типизацией, интерфейсами, обобщениями, модификаторами доступа и прочими страшными абстракциями.

Эпоха TypeScript не означает конец JavaScript — скорее, это новый этап в его эволюции. Динамическая типизация хороша для старта, но в сложных системах без строгих типов не обойтись. TypeScript становится всё популярнее, и это не просто хайп: он реально помогает избегать ошибок и ускоряет разработку в долгосрочной перспективе. Его точно стоит добавить в свой стек.
Следите за новыми постами
Следите за новыми постами по любимым темам
467 открытий3К показов