Прежде чем я начну свой рассказ, мне хотелось бы отметить, что вы сможете разобраться в этой статье, даже если никогда не пользовались 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-проектах?
dom1n1k
Я называю «жертвы фреймворков» тех, кто бросается в высокоуровневые инструменты, проигнорировав изучение основ. Сложение строк в JS — это такие азы, которые впитываются в подкорку через неделю практики, и забыть уже невозможно.
Это нельзя отнести к достоинствам языка, но строить всю аргументацию на такой малозначительной коллизии — смешно.
Almatyn
Сколько пафоса от специалиста по основам. А то, что в большом проекте, где голова забита другими сложностями, человек может элементарно ошибиться, нельзя понять?
Интересно, dom1n1k, имея в подкорке впитанные азы, вы не нуждаетесь в TypeScript? На чистом JS пишете? Наверняка нет. Добрее надо быть.
dom1n1k
Конечно можно ошибиться, все мы люди. Но когда ошибка настолько элементарная, достаточно просто хмыкнуть «блин, точно» и продолжить. А не расписывать, что JS устроил «The Storm» и дал «strong reason» для перехода на TS. Автор либо делает из мухи слона, либо (если он не опечатался, а реально не знал) — жертва фреймворков. Мой комментарий был об авторе и его статье, а не о TS.
Almatyn
Да это же всего лишь статья. Мы даже не знаем, реальный ли это случай, или воображаемый. Возможно пример со сложением строк слишком тривиален. Но это не повод для уничижительных ярлыков. Все мы находимся на разных ступеньках на лестнице познания.
E11E
Нечего делать в большом проекте тому, кто даже не работал с типизированными языкми и не принял за привычку объявлять переменые и принудительно подтверждать типы.
Я осуждаю даже тех, кто работая с типизированным языком, отключает опцию IDE, требующую явных объявлений и типизации.
Преобразование типов, замену разделителей дробной части, локальной денежной единицы, просто должно врасти в организм на уровне инстинктов. И выбирать мертвый язык вместо развивающегося, именно из-за своей проблемы, а не проблемы языка — это моветон.
3263927
золотые слова
turone
Согласен, тоже смутило:
Во первых, незнание основ. Во вторых, как без элементарной проверки и простого теста — прямо в работу? И в третьих — сразу переходим на другое и тоже без освоения основ и анализа? А если в TypeScript тоже вылезет специфичная проблема — то снова всё поменяем?
Лучше если проект в самом начале, то потратить немного больше времени на выбор фундамента проекта и когда обоснованный выбор сделан, еще немного на освоение основ этого фундамента, чтобы учесть его специфику, тогда в середине и на финише не будет разных сюпризов "вот этот ЯП нам всё поломал".