company_banner

Рассказ о том, почему в 2021 году лучше выбирать TypeScript, а не JavaScript

Автор оригинала: Danny Adams
  • Перевод
Недавно я, используя React Native, занимался разработкой мобильного приложения для медитации Atomic Meditation. Эта программа помогает тем, кто ей пользуется, выработать привычку медитировать, ежедневно уделяя этому занятию какое-то время. В ходе работы у меня появились серьёзные причины приступить к изучению TypeScript и начать пользоваться им вместо JavaScript в проектах среднего и крупного размера.

Прежде чем я начну свой рассказ, мне хотелось бы отметить, что вы сможете разобраться в этой статье, даже если никогда не пользовались React Native. Я буду всё подробно объяснять. А если вы делали какие-нибудь проекты на React, то, читая эту статью, можете считать, что React и React Native — это одно и то же.



А теперь расскажу о том, как обычный JavaScript втянул меня в неприятности.

День 1: всё идёт как надо


В React Native есть объект AsyncStorage, который представляет собой хранилище данных типа ключ/значение с асинхронным доступом к значениям по ключам. Он даёт разработчику очень простой механизм для организации постоянного хранения данных на мобильном устройстве пользователя.

Например, воспользоваться им можно так:

AsyncStorage.setItem("@key", value)

AsyncStorage позволяет хранить лишь строковые данные. Поэтому для того чтобы поместить в это хранилище число — это число сначала надо конвертировать в строку.

Ниже показано применение React-хука useState для объявления переменной sessionCount и для установки её начального значения в 0. Тут же имеется и функция setSessionCount, которая позволяет менять состояние sessionCount:

const [sessionCount, setSessionCount] = useState(0)

Предположим, пользователь завершил сеанс медитации (я, напомню, занимался разработкой приложения для медитации). В sessionCount хранится общее количество сеансов медитации, завершённых пользователем (я буду теперь называть этого пользователя «Anxious Andy» — «беспокойный Энди»). Это значит, что нам надо прибавить 1 к значению, хранящемуся в sessionCount. Для этого вызывается функция setSessionCount, в которой и выполняется прибавление 1 к предыдущему значению sessionCount. А потом количество завершённых медитаций нужно сохранить в AsyncStorage в виде строки.

Всё это надо сделать в некоей функции, которую я предлагаю назвать saveData:

// Пользователь завершил сеанс медитации…
const saveData = () => {
  setSessionCount(prev => {
    const newSessionCount = prev + 1

    AsyncStorage.setItem("@my_number", newSessionCount.toString())

    return newSessionCount
  })
}

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

День 2: затишье перед бурей


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

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

В коллбэке мы асинхронно получаем данные из хранилища, а после этого вызываем функцию setSessionCount(), передавая ей эти данные, то есть — «1»:

useEffect(() => {
  AsyncStorage.getItem("@my_number").then(data => setSessionCount(data))
}, [])

Беспокойный Энди успешно справляется с ещё одной медитацией. Поэтому к sessionCount надо добавить 1, что позволит сохранить общее число завершённых сеансов медитации.

Новое значение, как и прежде, мы записываем в хранилище:

// Пользователь завершил сеанс медитации…
const saveData = () => {
  setSessionCount(prev => {
    const newSessionCount = prev + 1

    AsyncStorage.setItem("@my_number", newSessionCount.toString())

    return newSessionCount
  })
}

К настоящему моменту пользователь завершил 2 сеанса медитации.

День 3: буря


Энди, теперь — уже вовсе не беспокойный, достаёт телефон и открывает приложение для того чтобы в третий раз подряд устроить сеанс медитации (и дела у него идут хорошо).

Он хочет узнать о том, как далеко продвинулся в деле выработки полезной привычки. Поэтому он открывает экран статистики. «О, да тут много всего интересного», — приговаривает он. «Отличная программа!».

Но его любовь к этой программе быстро сходит на нет…

Программа сообщает ему о том, что он провёл 11 сеансов медитации. А он-то медитировал всего два раза!


Неправильная статистика по сеансам медитации

Что пошло не так?


В первый день мы записали в sessionCount начальное значение — число 0.

Пользователь завершил сеанс медитации — поэтому мы добавили к sessionCount 1. Затем мы преобразовали то, что получилось, в строку — в «1», после чего записали это в асинхронное хранилище (вспомните — оно может хранить только строковые данные).

Во второй день мы загружаем данные из хранилища и записываем в sessionCount загруженное значение. То есть — «1» (строку, а не число).

Пользователь завершает сеанс медитации и мы прибавляем к sessionCount 1. А в JavaScript «1» + 1 равняется «11», а не 2.

Мы забыли преобразовать строковые данные, считанные из хранилища, в число.

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

JavaScript позволил нам свободно, не сознавая того, что мы делаем, поменять в ходе выполнения программы тип данных, хранящихся в переменной.

Решить эту и другие подобные проблемы можно с помощью TypeScript.

Что такое TypeScript?


Если вы не знакомы с TypeScript, то знайте, что это, в сущности, то же самое, что и JavaScript, но оснащённое некоторыми полезными возможностями. В частности, переменные не могут менять типы. А если это случится — TypeScript выдаст сообщение об ошибке.

Браузеры не могут выполнять TypeScript-код. Поэтому TypeScript-файлы проекта надо транспилировать в JavaScript. На выходе получится несколько JavaScript-файлов (или один большой «бандл» с JS-кодом проекта).

Использование TypeScript в React Native-проектах


Добавить поддержку TypeScript в существующий React Native-проект очень просто. А именно, надо будет кое-что установить из npm и сделать пару настроек.

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

После того, как изменено расширение файла, TypeScript разразится гневной тирадой о том, что аргумент типа 'string | null' нельзя назначить параметру типа 'SetStateAction<number>'.


TypeScript предупреждает разработчика о том, что с типами данных что-то не так

Это значит, что мне тут, чтобы избавиться от сообщения об ошибке, надо, во-первых, проверить data на null, а во-вторых — преобразовать из строки в число (воспользовавшись parseInt()):

useEffect(() => {
  AsyncStorage.getItem("@my_number").then(data => {
    if (data) {
      setSessionCount(parseInt(data))
    }
  })
}, [])

Использование TypeScript подталкивает разработчика к написанию более качественного и надёжного кода. Это просто замечательно!

По каким материалам изучать TypeScript?


Я изучал TypeScript по этому видеокурсу канала Net Ninja. И если бы мне надо было бы что-нибудь изучить, то я в первую очередь поинтересовался бы тем, нет ли на этом канале курса по тому, что мне нужно.

Кроме того, официальная документация по TypeScript очень даже хороша.

Итоги


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

Да, не могу не отметить, что JavaScript хорошо подходит для маленьких проектов. Но при работе над средними и большими проектами, а так же — над маленькими проектами, которые вполне могут вырасти, лучше, пожалуй, прибегнуть к TypeScript, даже если для этого придётся потратить время на его изучение. А если вы знаете JavaScript, то и TypeScript освоите без особого труда.

Используете ли вы TypeScript в своих React-проектах?


RUVDS.com
VDS/VPS-хостинг. Скидка 10% по коду HABR10

Комментарии 50

    +25
    Я называю «жертвы фреймворков» тех, кто бросается в высокоуровневые инструменты, проигнорировав изучение основ. Сложение строк в JS — это такие азы, которые впитываются в подкорку через неделю практики, и забыть уже невозможно.
    Это нельзя отнести к достоинствам языка, но строить всю аргументацию на такой малозначительной коллизии — смешно.
      –7
      Сколько пафоса от специалиста по основам. А то, что в большом проекте, где голова забита другими сложностями, человек может элементарно ошибиться, нельзя понять?
      Интересно, dom1n1k, имея в подкорке впитанные азы, вы не нуждаетесь в TypeScript? На чистом JS пишете? Наверняка нет. Добрее надо быть.
        +12
        Конечно можно ошибиться, все мы люди. Но когда ошибка настолько элементарная, достаточно просто хмыкнуть «блин, точно» и продолжить. А не расписывать, что JS устроил «The Storm» и дал «strong reason» для перехода на TS. Автор либо делает из мухи слона, либо (если он не опечатался, а реально не знал) — жертва фреймворков. Мой комментарий был об авторе и его статье, а не о TS.
          –4
          Да это же всего лишь статья. Мы даже не знаем, реальный ли это случай, или воображаемый. Возможно пример со сложением строк слишком тривиален. Но это не повод для уничижительных ярлыков. Все мы находимся на разных ступеньках на лестнице познания.
          –4
          Нечего делать в большом проекте тому, кто даже не работал с типизированными языкми и не принял за привычку объявлять переменые и принудительно подтверждать типы.
          Я осуждаю даже тех, кто работая с типизированным языком, отключает опцию IDE, требующую явных объявлений и типизации.

          Преобразование типов, замену разделителей дробной части, локальной денежной единицы, просто должно врасти в организм на уровне инстинктов. И выбирать мертвый язык вместо развивающегося, именно из-за своей проблемы, а не проблемы языка — это моветон.
            –2

            золотые слова

              0
              Чушь собачья в первом абзаце, простите. Люди делали много лет до появления TypeScript, делают и будут делать качественные продукты для веба на JavaScript. Если вы на постоянной основе успешно используете JavaScript, то должны были ощущать, что TypeScript — это никому не нужные оковы, которыми так нравится в последнее время увешивать как минимум фронт. Господа, никакие шестнадцатиэтажные конструкции с дюжинами треугольных скобок на фронте не упрощают жизнь, это вас кто-то обманул ). Имеющихся типов вполне достаточно, чтобы работать с любыми данными вашего апи, которое в принципе содержит в себе все структуры нужные вам. Единственный полезный момент для тайпскрипта видится в автоматичекой генерации типизированных обёрток для запросов, чтобы поудобнее было в ИДЕшечке. В остальном оно — лютый тормоз любого проекта )
            +5

            Согласен, тоже смутило:


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

            Во первых, незнание основ. Во вторых, как без элементарной проверки и простого теста — прямо в работу? И в третьих — сразу переходим на другое и тоже без освоения основ и анализа? А если в TypeScript тоже вылезет специфичная проблема — то снова всё поменяем?
            Лучше если проект в самом начале, то потратить немного больше времени на выбор фундамента проекта и когда обоснованный выбор сделан, еще немного на освоение основ этого фундамента, чтобы учесть его специфику, тогда в середине и на финише не будет разных сюпризов "вот этот ЯП нам всё поломал".

            +10
            AsyncStorage позволяет хранить лишь строковые данные.

            Так, а причём тут проблема JS, если изначально хранилище только для строк?
            Тут проблема невнимательности разработчика.
            Каким образом ЯП должен угадать намерение хранить числа в строковом типе?
              +3
              Ровные пацаны хранят там JSON
                –6
                Как хранение в JSON убережет от сложения строк?
                  +6
                  Тем что в JSON будет записано число, а не строка. И обратно будет прочитано число.
                    +4

                    Обратно из JSON будет прочитано any, которое надо еще как-то провалидировать перед кастованием типа. А этого уже Typescript из коробки не даёт.

                      +7
                      Точно. А потом решили отрефакторить название переменной, и вот уже у Беспокойного Энди, который уже месяц медитирует каждый день стало nan сессий или undefined сессий… А у Нервного Джо, который скачал приложение только сегодня, все нормально работает. Разбирайтесь теперь с этим. И как там TS, сильно помог? До сих пор спокойно спится?
                      Если что, это не критика в сторону TS (обожаю его). Просто по статье так прямо он все проблемы решает и можно расслабиться…
                0

                Так это проблема js, почему он позволяет передавать строку в функцию которая ожидает только nunber?

                  0

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

                  А язык такой, какой есть, странно винить его за это.

                    –5

                    Эм. По вашей логике язык C не виноват в том что в коде на С встречаются ошибки выхода за пределы массива? Тут претензия к разработчикам которые знают что у массива есть размер и за его пределы писать нельзя, нужно делать проверку через strlen или хранить размер массива в отдельной переменной.

                    "А язык такой, какой есть, странно винить его за это."

                    Все верно?

                      0
                      Полагаю, что да. Тут два варианта: или при разработке языка это зачем-то было нужно, или разработчик языка был настолько недалёким, что не предвидел такой вариант. И раз это не исправили в последующих стандартах, значит нужна обратная совместимость. Но совместимость с ошибкой вместо её исправления — это дурость. Получается, есть какие-то случаи, когда это не ошибка?
                        +1
                        в чем виноват C?, в том что разраб «идиот»? тогда все яп виноваты что на них в том числе и не самые хорошие программисты пишут, ни когда не задумывался почему C до си пор жив, почему яп со слабой типизацией все еще востребован, может у него есть какая то ниша где именно такое его поведение оправдано и дает свои плюшки, или ты из тех у кого всегда кто то виноват, но только не ты
                    0
                    Каким образом ЯП должен угадать намерение хранить числа в строковом типе?

                    Type checker может дать по рукам, если вы будете обращаться со строкой как с числом. IDE может подсказать тип переменной, что также снизить вероятность ошибки.

                    –1
                    я могу спать немного спокойнее, зная о том, что переменные в моём проекте не могут совершенно неожиданно менять свои типы.

                    Я вот не могу спать спокойно, потому что теперь у меня куча забот чтобы правильно все это хозяйство типизировать.
                    Когда сидишь и разбираешься, почему tsc выдает ошибку там, где все правильно и на js код в этом месте работает без проблем, иногда очень жалко времени.
                      +1

                      Это время, которое вы потом сэкономите на отладке и поддержке.

                        0
                        И еще на документировании. Всякие массивы ключ — значение теперь описаны в виде типов. Очень удобно.
                          +3

                          И на автокомплите. Вебшторм реально тащит в подсказках, когда TS.

                      +8
                      Тестировать не пробовали?

                      Кроме того, вы сами преобразовывали число в строку перед помещением в хранилище. Что побудило вас не делать обратное преобразование?
                        0
                        Тестировать не пробовали?

                        Действительно, зачем полагаться на тайпчекер, если можно написать свой маленький частный случай тайпчекера самому?


                        Что побудило вас не делать обратное преобразование?

                        Забыли после рефакторинга. Или код по считыванию писал другой человек. Или просто не выспался и ошибся.

                          +1
                          Рефакторинг — изменение кода с сохранением функциональности. Что поддерживается или тестами или инструментами для рефакторинга, следящими за сохранением функциональности.

                          Типизация — это форма авансового платежа. Вы платите заранее, не будучи уверенным пригодится вам или нет.

                          Тестирование — тоже плата вперед. Только все мы видели большие системы без типов, и никто не видел больших систем без тестов.
                            +1

                            Только вот ROI у этих вещей сильно разный.


                            И потребные тесты в зависимости от типизации тоже сильно разные. Уверяю вас, что тесты в компиляторе ghc, написанном на хаскеле, отличаются по смыслу и целям от тестов для кода на JS. Или был у меня проект, немаленький — там тесты были только интеграционные, «на этом входе вся система даёт вот такой выход», и каждый раз после рефакторинга тесты были сразу зелёные, как только тайпчекер был доволен. Или вот другой мой проект сейчас — там тестов вообще нет, одни типы, но доверяю я ему куда больше, чем любому проекту с тестами, что я видел.

                          0

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

                          +1

                          Скажите а как сейчас дела с типами? Нету рассинхрона между последними версиями библиотек и их типами?

                            0

                            Если тайпинги поставляются вместе с библиотекой — то откуда рассинхрону взяться? А вот если они внешние — то да, от рассинхронов не избавиться.

                              –1
                              Говорят yarn-2 автоматически разруливает @types-зависимости.
                              –1

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

                                0

                                Swagger+io-ts. И будет вам счастье

                              +2
                                0
                                Шедеврально конечно )))
                                –6

                                Немного непонятно, зачем использовать ts в react. Как по мне, проще взять сразу angular, а когда предпочтительнее использовать react - оставить js. Может быть я не прав, но мне кажется, что лучше использовать языки в их родной среде(хоть ts и является по сути js,но все же)

                                  +3

                                  Почему вы считаете, что Typescript для React не является "родной средой"? Вообще-то, поддержка Typescript в React является более полной, нежели в Ангуляре.

                                  +5
                                  TS — это статический типизатор (static type checker) для JS, причем, далеко не первый и, возможно, не последний, поэтому правильнее говорить об использовании TS в дополнение к JS, а не вместо него. Плохое знание JS является недостатком разработчика, а не языка: const newSessionCount = +prev + 1 и проблема решена. Либо, если приходится часто работать с хранилищем, можно создать такую утилиту (на примере LocalStorage):
                                  export const storage = ((s) => ({
                                    set: (key, value) => s.setItem(key, JSON.stringify(value)),
                                    get: (key) => JSON.parse(s.getItem(key)),
                                    key: (n) => s.key(n),
                                    remove: (key) => s.removeItem(key),
                                    clear: () => s.clear()
                                  }))(window.localStorage)
                                  
                                    +2
                                    К тому же, автор традиционно не перечислил в статье инструменты, которые необходимо установить для работы с TS.
                                      0

                                      Хотел упомянуть, что есть программисты разной квалификации и не всегда есть поставленный процесс отбора тех кто «привык не совершать ошибки». Даже обычный спеллчекер в ide уже отсекает определенное количество грамматических ошибок (которые имеют место быть). Если расценивать тс как спеллчекер для грамматики языка, то грех не пользоваться, даже если ты умеешь не совершать ошибки в js без надстроек.

                                      –1

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

                                        0

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

                                        0
                                        Рассказ о том, почему в 2021 году лучше выбирать TypeScript, а не JavaScript

                                        Так себе аргументация "за". Типа, в 2019 таких проблем не стояло?

                                          0

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

                                            +1

                                            TS похож на C# что делает его более менее сносным на клиенте, но проблема в том что добавляется дополнительный шаг сборки, и теперь вместо нажатия зелёной кнопочки play в visual studio нужно запускать какие-то сборки и ждать дополнительного шага компиляции... гемор который не всегда стоит свеч

                                              0

                                              Погодите, а что случилось с зелёной кнопочкой в visual studio?

                                                0

                                                Когда речь заходит о фронтенде/реакт нейтиве то в 99% случаев транспиллинг уже используется, так что разница незаметна. А вот для маленьких проектов под node.js замечание справедливо.

                                                +3

                                                Стартанули проект на тайпскрипте, до этого пять лет поддерживал проект на чистом js. Конечно тайпскрипт не решает проблему дизайна приложения, но значительно снижает человеческий фактор. Код минимум стал потдатлив к элементарному рефакторингу. В прошлом проекте браться рефакторить старую часть было почти что табу, потому как ничто не гарантировало минимальной работоспособности. Если можно снизить влияние человеческого фактора, то почему бы и нет?

                                                  0
                                                  Немного не в тему…
                                                  Для вычислений использую Big.js. Это конечно еще одна библиотека, но она дает понять при чтении кода «здесь что-то вычисляется» и строже в арифметических действиях, что лишний раз защищает от нелепых багов.

                                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                  Самое читаемое