javascript

Когда нужен TypeScript: введение в мир надежного программирования

  • суббота, 12 октября 2024 г. в 00:00:04
https://habr.com/ru/companies/alfa/articles/849850/

Поговорим про введение в TypeScript и то, как с помощью него мы можем сделать наше программирование и наш код на JavaScript более надежными и прогнозируемыми. 

Меня зовут Александр Чернов, я фронтенд-разработчик в Альфе, веду подкаст «ТИНОИД», обожаю плавание и влюблён в веб и фронтенд-разработку.

Всё ли так хорошо в мире программирования на JavaScript? 

Представим, что у нас есть простая функция add, которая принимает два неких параметра и выполняет сложение или конкатенацию, если мы передаем строки. 

В примере мы передаём числа и ожидаем, что программа не сломается и с ней всё будет в порядке, надеемся, молимся. 

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

Давайте посмотрим, что будет, если мы наведём курсор на функцию и посмотрим на подсказку от IDE.

IDE говорит, что здесь есть функция add, которая принимает параметры a и b и что-то возвращает. И это «что-то», так называемое обозначение, подписано как any

В контексте примера any говорит о том, что наша функция add и наша среда разработки понимает, что здесь мы можем передать что угодно, потому что any — это отсутствие какого-то определенного типа. Даже если мы ничего не передадим, то не увидим какой-либо ошибки, ведь мы ничего конкретно не обозначили.

В следующем примере уже другая функция — double

Мы ожидаем принять параметр a, но при записи выполнения её результата (на пятой строке) мы не передаем аргумент.

Фактически, мы должны были бы получить ошибку о том, что мы что-то ожидаем от функции, но ничего не передаём. Но так как тип обозначения any, то наша функция не будет ругаться на то, что мы что-то забыли, даже если мы оставим аргументы пустыми.

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

И даже если мы расширим аргументы и передадим туда что-либо еще, наша функция, конечно, отработает с первым аргументом. Но, тем не менее, зачем нам остальные, вдруг мы что-то сломаем? А такую ошибку дебажить будет очень трудно, особенно, когда мы ожидаем, что код написан правильно. 

JSDoc

Чтобы писать на JavaScript было попроще, разработчики придумали такую вещь, как JSdoc. Это специальный декоратор, при помощи которого мы можем описывать наши функции. Позволяет создавать описание к функциям, а также к параметрам и возвращаемым типам. 

Соответственно, чтобы описать какой-либо параметр, нам необходимо включить вот такую конструкцию: 

  • Описать в param то, что мы ожидаем принять, аргумент, и тип этого аргумента. В данном случае аргумент a типа number.

  • В returns мы пишем, что мы вернём. В моём примере мы ожидаем, что тоже вернём какое-то число.

  • И в description описываем, что это такое: как с функцией работать, как мы можем её использовать.

Представим, что мы всё описали, сделали npm пакет, другой разработчик заимпортит к себе функцию и будет использовать. Но, что если, он не прочитал описание и точно так же будет использовать три аргумента, как мы пробовали ранее? Если разработчик прочитает код по описанию, но захочет пойти другим путем, ничто ему не помешает это сделать. В таком случае мы снова не увидим ошибку на этапе написания кода. 

Иными словами, JSdoc нас не спасёт от поиска ошибок во время разработки. 

Плавно подходим к TypeScript 

Языки программирования можно поделить на две категории: с динамической типизацией и статической.

 

В языках программирования с динамической типизацией, как JavaScript, на этапе написания года мы не знаем как и с какими данными работает наша программа и какие типы у этих данных.

В языках со статическое типизацией, в TypeScript, проверка кода на корректность/ошибки проводится до запуска кода,.

TypeScript — это надмножество — (расширение/надстройка) JavaScript, которое добавляет типы (программные).

TS — это тот же JS, но с типами. 

Типы — это множество значений, которыми мы можем описать наши переменные, функции. Типы в TypeScript используются для определения и описания данных, которые могут быть определены и использованы в программе. Мы можем использовать типы значений, такие как number, boolean, string и так далее.

Не буду долго теоретизировать, перейду сразу к примеру. 

Чем нам может помочь TS?

Чтобы использовать TS, нам необходимо добавить пакет в свой проект и проинициализировать его.
npm i typescript -D / npx tsc --init и обязательно поменять расширение файла на .ts
После этого наш TypeScript включается в работу. Конечно, его настраивать нужно немножко больше, но здесь я намеренно упростил процесс. 

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

Мы видим, что наш параметр a на самом деле any: мы можем передать что угодно, а можем вообще не передавать. Для TypesScript это ошибка. Но главное, что мы видим её сразу же — на этапе написания кода. И сразу же можем понять, в чём ошиблись и как ошибку исправить. 

И просто так вызвать нашу функцию с пустыми аргументами мы уже не сможем, потому что нам надо явно что-то указать: мы ожидаем туда передать один аргумент, а передаём ноль — это ошибка.

Давайте посмотрим пример того, как мы можем типизировать нашу функцию double

Мы предполагаем работу с числами, поэтому можем описать тип number у параметра a. Возвращаемый тип тоже будет number, так как мы ожидаем получить именно его на выходе.

И теперь при вызове нашей функции, если мы обратимся к подсказкам нашей IDE и наведём мышку на вызов функции double, мы увидим, что она ожидает явно уже не any, а тип number у аргумента, и вернем мы также number

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

Где еще нам пригодится TS? Рассмотрим одну типовую задачу.

Использование TypeScript с API

Рассмотрим пример получения данных с какого-то эндпойнта и их отображения в нашей верстке. На иллюстрации пример на JS (React). 

Здесь мы заводим некоторый стэйт, который будет хранить массив наших пользователей. В useEffect мы делаем запрос к эндпойнту, и затем в JSX-разметке по условию возвращаем наш массив пользователей. 

Когда я пишу атрибут key я бы хотел, чтобы моя IDE подсказала, какие поля я могу использовать у моего пользователя. Если я попробую вызвать подсказку в данном примере на JSX-расширении без TypeScript, то, к сожалению, я не получу ничего путного: появятся какие-то системные сообщения и типы, которые не помогут мне в разработке.

Как это можно исправить? Для начала необходимо обратиться к документации нашего бэкенда и узнать какие типы и JSON мы можем получить с нашего эндпойнта. Это можно делать с помощью Swagger / Описания в документации или тех инструментов, что вы используете в команде или просто сходить на эндпойнт и посмотреть, какой JSON мы получаем.

В данном случае мы видим, что у нас есть массив объектов user с определёнными полями, которые мы точно должны получить. 

Давайте возьмем парочку из них. Например, для атрибута key нам подойдёт id и, допустим, имя name

Чтобы наш компонент типизировать нам необходимо, естественно, поменять расширение на .tsx, а затем завести специальный тип User. И описать его вот в таком синтаксисе. 

Мы явно указываем, что наш тип User будет в себе содержать id с типом number и name с типом string:

После этого мы можем сразу типизировать наш стейт. Для этого скажем ему, что теперь он может принять не просто некий null (как начальное значение), но также должен учитывать, что в типе может прийти либоnull при ошибке запроса, либо массив объектов с типом user, который мы описали выше.

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

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

Про d.ts файлы

Бывает и так, что мы типизировали свой код, но потом используем чужой не типизированный модуль/компонент API. Это довольно частая проблема, особенно если мы используем какую-то непопулярную библиотеку для решения. Здесь есть решение — d.ts файлы. Файлы с расширением .d.ts содержат декларации типов для TypeScript, которые содержат описание типов без реализации. Они позволяют компилятору TypeScript понимать структуру данных, функций и классов, которые не были написаны на TypeScript (например, библиотеки JavaScript).

Использование TypeScript с Props

Ещё один частый кейс — применение TypeScript в контексте props. 

Думаю, многие знают такую боль, когда забываешь написать props (если это расширяющийся проект, а их всегда нужно держать голове). А TypeScript помогает типизировать props и ничего не забыть. 

Например, я хочу описать какой-то props. Я буду ожидать, что в мой компонент UsersList я буду передавать какой-то заголовок типа header. Для этого описываю специальный тип Props и говорю ему, что в нём будет содержаться поле header с типом string, то есть строкой. Также я этот тип использую в описании к типу Props уже в самом компоненте. 

И после, когда я буду использовать компонент в моей разметке, то в компоненте при написании кода буду видеть ошибку ниже.

Мне не нужно запускать программу или держать в голове те props, которые я бы хотел использовать — я могу «забыть» их написать, а с TypeScript - всегда увижу ошибку и смогу поправить. Удобно. Главное не забывать описывать props!

Подытожим 

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

Потенциальные ошибки видно сразу — на этапе написания кода. Это, естественно, значительно сокращает время (почти полностью в некоторых кейсах) на отладку. Например, если я попытаюсь присвоить строку числовой переменной, компилятор TypeScript сразу же сообщит об ошибке.

Читаемость кода. Чётко определенные типы данных делают код более понятным как для меня, так и для других разработчиков, работающих над проектом. Это особенно важно при работе в команде или над долгосрочными проектами. Становится проще погрузиться в проект и понять, какой здесь поток данных, что мы с ними делаем. В большие проекты на JS довольно трудно быстро «въехать», но TS помогает и с этим — ты видишь, какие данные какой компонент принимает, и можешь построить логическую цепочку. На «чистом» JS всё витает где-то в воздухе. 

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

Более надежные API. TypeScript помогает создавать более надежные и предсказуемые API, как внутри компонента, так и между компонентами. Например, я могу точно определить тип данных, которые принимает и возвращает функция, что минимизирует риск возникновения непредвиденных ошибок, и другие разработчики так же быстро смогут понять, что возвращается на сервер и что с этим можно сделать.

Если вам нужна статическая типизация, если хочется больше понимать своё приложение, иметь больше надёжности, то рекомендую. Да, конечно старт проекта с использованием TS не такой быстрый (хотя сейчас существуют очень много инструментариев для TypeScript, большое кол-во автоматизаций), но это определенно точно хорошая инвестиция в проект!

Правда если вы решили внедрять TS на проект, что работает долгие годы и уже на поддержке, то, возможно, не стоит тратить ресурсы на то, чтобы добавить TypeScript. Здесь уже бизнес будет решать, выгодно это или нет. А разработчики могут использовать например JSDoc.

В качестве материалов для изучения TypeScript я могу посоветовать следующие:

А также отличный онлайн-тренажер с возможностью выбора уровня сложности.