Одна из самых оживленных дискуссий в области разработки программного обеспечения заключалась в том, предпочтительнее ли статический набор текста динамическому.
Статическая типизация предполагает использование инструмента для проверки типов кода во время реализации — вы пишете код, а инструмент проверки типов ищет любые типы, которые несовместимы, без компиляции или выполнения кода. При динамическом наборе текста программное обеспечение проверяет, совместимы ли типы при выполнении кода.
Хотя лучше получать уведомление об ошибке раньше, чем позже, статическая типизация создает дополнительную работу при написании кода. Это требует добавления правильных аннотаций типа. В частности, старые языки, такие как Java или C++, налагают значительную нагрузку на разработчика, в то время как их статические проверки типов часто не позволяют обнаружить важные ошибки. Например, Java требует подробных аннотаций типов, в то время как общие аннотации могут привести к потере информации о типе во время выполнения.
С TypeScript статическая типизация вошла в экосистему JavaScript. Это язык программирования, созданный в 2012 году Microsoft, который компилируется в JavaScript. TypeScript похож на JavaScript, с различиями в аннотациях типов внутри кода.
В следующем примере версия TypeScript содержит аннотации типов, которые сообщают разработчику, какие типы ожидает функция add.
// JavaScript function add(x, y) { return x + y } // TypeScript function add(x: number, y: number) { return x + y }
Изначально JavaScript использовался как второстепенный язык в браузерах, позволяющий создателям добавлять интерактивные функции на веб-сайты. Сегодня на JavaScript пишутся полноценные клиентские приложения. TypeScript помогает работать с этими большими кодовыми базами и делает их более управляемыми.
В этой статье мы познакомимся с TypeScript и поможем определить, когда использовать его в проекте. Начнем с причин использования TypeScript в первую очередь. В конце концов, это включает этап компиляции и требует от разработчиков переосмысления того, как они будут реализовывать различные функции.
JavaScript позволяет определять только имя функции и количество аргументов, которые она принимает. С помощью TypeScript вы также можете определить тип аргументов и то, какой тип возвращает функция.
Давайте рассмотрим следующий пример класса Logger:
// JavaScript class Logger { #serverUrl constructor(serverUrl) { this.serverUrl = serverUrl } log({logLevel, message}) { fetch(this.serverUrl, { method: "post", body: JSON.stringify({logLevel, message}) }) } // TypeScript interface LogMessage { logLevel: "info" | "danger" message: string } class Logger { #serverUrl: string constructor(serverUrl: string) { this.serverUrl = serverUrl } log({logLevel, message}: LogMessage): void { fetch(this.serverUrl, { method: "post", body: JSON.stringify({logLevel, message}) }) } }
В версии JavaScript нет способа определить, какие типы данных принимают методы. На этапе планирования, когда реализация этих методов еще не определена, вам необходимо использовать пользовательские средства для объяснения желаемых типов.
С TypeScript сигнатуру функции легче понять благодаря интерфейсам, которые раскрывают значительно больше деталей, чем версия JavaScript.
Инструменты моделирования данных и API позволяют экспортировать больше информации о структурах данных с помощью TypeScript, чем JavaScript, что делает эти инструменты более мощными и тесно интегрированными с вашей кодовой базой.
TypeScript может выявить потенциальные крайние случаи при написании кода — например, при использовании данных, которые асинхронная функция установит в будущем.
//JavaScript class Profile { #name #birthday async load() { const {name, birthday} = await fetchProfile() this.name = name this.birthday = birthday } getAge() { const date = new Date(Date.now() - this.birthday.getTime()); return Math.abs(date.getUTCFullYear() - 1970); } } // TypeScript class Profile { #name?: String #birthday?: Date async load() { const {name, birthday} = await fetchProfile() this.name = name this.birthday = birthday } getAge() { const date = new Date(Date.now() - this.birthday.getTime()); return Math.abs(date.getUTCFullYear() - 1970); } }
В примере JavaScript вы не получите ошибку до вызова getAge
метода перед методом load. Большую часть времени это будет работать нормально, но новый разработчик потенциально может забыть вызвать метод load
или проверить необязательную переменную перед его использованием и, таким образом, привести к сбою системы.
В TypeScript реализация метода getAge
приведет к ошибке. Поле this.birthday
может быть undefined
потому что мы пометили его как необязательное с помощью оператора . ?
Большая часть разработки программного обеспечения сегодня заключается в использовании программного обеспечения сторонних производителей для создания собственного поверх него. TypeScript предоставляет более подробную информацию об API сторонних библиотек, чем JavaScript — это похоже на наличие ссылки на API, но внутри вашей IDE.
В примере на рис. 1 TypeScript предоставляет две части информации для класса Lib: что его конструктору требуется один строковый аргумент и что конструктор является закрытым, поэтому вызов к нему приведет к ошибке во время выполнения.
При открытии кода коллеги—разработчика могут обнаружиться непонятные реализации — мало чем отличающиеся от просмотра собственного кода месячной давности. Разработчики склонны избегать комментариев, но TypeScript может помочь.
Внутри кодовой базы есть дополнительная информация, которая может прояснить ситуацию. Определение функции, которая принимает объект, мало что показывает, но просмотр фактических типов всех полей объекта позволяет получить более четкую картину.
Давайте рассмотрим следующий пример:
interface LibConfig { key: string limit?: number } export class Lib { private constructor(path: string) {} static create(path: string, config?: LibConfig) { return new Lib(path) } }
Поскольку создатель класса Lib
использовал TypeScript, мы можем проверить интерфейс LibConfig
, чтобы увидеть, какие опции предлагает этот объект конфигурации. Если бы разработчик использовал JavaScript, нам теперь нужно было бы проверить реализацию метода create
, чтобы увидеть все варианты. В приведенном выше случае это не проблема, но при большом методе процесс может быстро стать утомительным.
Комментарии полезны, но они не будут автоматически заполняться IDE.
Вот где TypeScript блистает. Возможности рефакторинга TypeScript являются значительным преимуществом при создании огромных баз кода. JavaScript обычно игнорирует тип переменной и позволяет передавать содержимое переменных, не глядя на него; в конце концов, объект есть объект.
С помощью TypeScript вы можете предоставить информацию о типе объекта вместе с текстом. Измените поле, и TypeScript отобразит все используемые поля в базе кода; более того, IDE может использовать эту информацию для автоматического рефакторинга всех этих местоположений.
В следующем примере мы изменим поле limit из предыдущего примера с необязательного на обязательное:
interface LibConfig { key: string limit: number }
Мы получим сообщение об ошибке в каждом файле, использующем этот интерфейс.
Ошибка на рисунке 2 ясно показывает, что передаваемому объекту не хватает обязательного ограничения поля. Если метод create
использовался в сотнях файлов, эта ошибка будет чрезвычайно полезна — эти файлы будут автоматически проверены и помечены ошибкой, чтобы разработчик не забывал исправлять каждую проблему.
TypeScript несовершенен. Он не может фиксировать все ошибки; однако некоторые тесты для реализации JavaScript часто устаревают. Например, нет необходимости проверять, случайно ли вы обратились к необязательному полю в параметре или как функция реагирует, когда ей предоставляются строковые аргументы вместо чисел.
Для некоторых проектов JavaScript может быть лучшим выбором. С ним быстрее начинать, и без этапа компиляции он хорошо подходит для более быстрых итераций. Но остается вопрос, перевешивают ли его преимущества недостатки, такие как потенциальные ошибки, возникающие в результате отсутствия статической типизации.
JavaScript старше, чем TypeScript, и за эти годы приобрел множество последователей. Если главной заботой является поиск талантов для проекта, JavaScript будет более безопасным выбором, чем TypeScript. Более простой синтаксис также понятен начинающим разработчикам, поэтому адаптация проходит более гладко.
Преимущество JavaScript заключается в том, что он запускается непосредственно в среде выполнения JavaScript, обеспечивая более высокую производительность и более удобный опыт разработчика. Кроме того, при ежедневном развертывании частых сборок стоимость выполнения этапов компиляции в конвейере непрерывной интеграции может быть снижена с помощью JavaScript.
Разработчики с большим опытом написания JavaScript могут почувствовать, что TypeScript замедляет их работу. Хотя оба варианта допускают одинаковый стиль кода, код, который не является идиоматическим для JavaScript, часто проще писать, что приводит к различным реализациям в TypeScript. JavaScript является допустимым синтаксисом TypeScript, но если правильные типы будут добавлены позже, TypeScript может потребовать чрезмерных аннотаций типов для обеспечения правильной реализации.
Кроме того, TypeScript может помешать вам написать определенный код, потому что он видит крайний случай, который может привести к ошибке, даже если она почти наверняка не возникнет, потому что вы проверили ее в другой части приложения. Это может привести к разочарованию при использовании TypeScript.
Инструменты ломаются и требуют обслуживания — чем меньше инструментов требуется для выполнения работы, тем лучше. Поскольку этап компиляции не требуется, сборка с помощью JavaScript может привести к упрощению процесса разработки: вы можете выполнять код таким, каким видите его в редакторе. Это может упростить поиск ошибки, поскольку между кодом и приложением проходит меньше шагов, которые могут вызвать проблему.
JavaScript довольно сильно изменился за последнее десятилетие. Хотя первоначально он служил небольшим скриптовым языком для браузеров, теперь JavaScript используется для больших приложений.
Такое более широкое использование вызвало определенные проблемы: большая база кода требует более организованной структуры, но JavaScript не имеет модульной системы. И хотя браузерные приложения взаимодействуют с сетью довольно часто, JavaScript предоставляет разработчикам только обратные вызовы, что делает обработку ошибок утомительной.
Эти проблемы побудили сообщество JavaScript предложить изменения в языке на протяжении многих лет. В язык была добавлена собственная модульная система, созданная для работы с модулями, распространяемыми по сети и загружаемыми по запросу.
Были добавлены асинхронные функции, чтобы сделать асинхронное программирование более похожим на синхронное программирование, превращая JavaScript в подходящее программное обеспечение, работающее с сетью.
Хотя начать новый проект с TypeScript проще, чем добавлять его позже в процессе, это не всегда возможно — и это также не является обязательным требованием.
TypeScript может быть постепенно внедрен в кодовую базу. Он может в некоторой степени проверять код JavaScript, и вы можете добавлять определения типов вне JavaScript, чтобы упростить его использование.
Кроме того, TypeScript понимает комментарии JSDoc, которые позволяют вам аннотировать типы в комментариях JavaScript, поэтому код остается простым JavaScript, при этом большая часть проверки типов сохраняется.
TypeScript — мощный инструмент для улучшения кодовых баз JavaScript. Он особенно эффективен при использовании библиотек сторонних производителей или рефакторинге вашего кода, предоставляя предложения в IDE и значительно улучшая работу разработчиков.
Однако TypeScript имеет недостатки: он требует дополнительных инструментов и добавляет этап компиляции, и в результате он менее подходит для работы с прототипами или небольшими проектами. Кроме того, опытные разработчики JavaScript могут обнаружить, что TypeScript требует от них написания реализаций способами, которые в JavaScript могут показаться чрезмерно загроможденными.