Калькулятор на телефон как способ знакомства с React-native

от автора

Приветствую.

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

Введение

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

Первым относительно серьезным(по крайней мере, работающим, а не показывающим работоспособность QuickStart`a) приложением для меня стал калькулятор — примерно такой же, как есть в каждом из смартфонов. Калькулятор должен был содержать один экран, в котором есть 2 раздела — дисплей и клавиатура, дисплей — отображает информацию, а клавиатура позволяет ее вводить.

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

Spoiler
Горизонтальное отображение
Горизонтальное отображение
Вертикальное отображение
Вертикальное отображение

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

Анализ исходного кода

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

let labels=[     ["1","2","3"],     ["4","5","6"],     ["7","8","9"],     ["0", ".","+/-"],     ["+","-","*","/"],     ["ln","C", "=",] ]

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

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

Spoiler
let functionMapping = {         "+":()=>{             setOperation(()=>(a,b)=>{return a+b})             setFirstOperand(display);             setDisplay("")         },         "-":()=>{             setOperation(()=>(a,b)=>{return a-b})             setFirstOperand(display);             setDisplay("")         },         "*":()=>{             setOperation(()=>(a,b)=>{return a*b});             setFirstOperand(display);             setDisplay("")         },         "/":()=>{             setOperation(()=>(a,b)=>{return a/b});             setFirstOperand(display);             setDisplay("");         },         "ln":()=>{             setOperation(()=>(a,b)=>{return Math.log(a)});             setFirstOperand(display);         },         "C":()=>{             setFirstOperand("");             setsecondOperand("");             setOperation(null);             setDisplay("");         },         "+/-":()=>{             setDisplay((+display)*(-1)+"");         },         ".":()=>{             if (display.indexOf(".")===-1)                 setDisplay(display+".")         },         "=":()=>{             setsecondOperand(display);             let rezult = operation(+firstOperand, +display);             setDisplay(rezult);         }     }     for (let i =0; i<10; i++) {         functionMapping[i+""]=()=>{setDisplay(display+i)};     }

Тут особого внимания заслуживают конструкции вида

setOperation(()=>(a,b)=>{return a * b})


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

setOperation(()=>{return ab})

весьма посредственно гуглится.

Однако, в статье рассматривается данная проблема — не только ее решение, но и причины, почему это было сделано именно так.

Кроме того, для числовых значений кнопок функция-хэндлер однотипна, поэтому добавление ее обработчиков вынесено в отдельный цикл.

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

    const [operation, setOperation] = useState(null);     const [firstOperand, setFirstOperand] = useState("");     const [secondOperand, setsecondOperand] = useState("");     const [display, setDisplay] = useState("");

Соответственно, firstOperand и secondOperand хранят значения переменных, display отвечает за информацию, выводимую на экран, а operation хранит ключ выбранной операции.

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

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

Spoiler

Отображение компонентов

<View style={styles.root}>       <View style = {styles.display}>           <Text style={{fontSize: 40}}>               {display}           </Text>       </View>         <View style={styles.keyboard}>         {labels.map((value, index, array)=>{           return <View style={styles.row}>               {value.map((item)=>{                   return <TouchableOpacity style={styles.cell} onPress={()=>{functionMapping[item]()}}>                       <Text style={{fontSize: 35}}>{item}</Text>                   </TouchableOpacity>               })}           </View>         })}       </View>     </View>

Стили

const styles = StyleSheet.create({   root: {       flex: 1,       backgroundColor: '#fff',       alignItems: 'center',       justifyContent: 'center',       fontSize:40   },   display:{       flex: 2,       backgroundColor: "lightblue",       width: "100%",       justifyContent: "space-around",       alignItems: "center"   },   keyboard:{       flex: 9,       width: "100%",       backgroundColor:"lightgrey",       justifyContent:"space-around",       alignItems:"center"    },   row:{       flex: 1,       flexDirection:"row",       justifyContent:"space-around",       alignItems:"center",       width:"100%",   },   cell:{       flex:1,       borderWidth:1,       width:"100%",       height:"100%",       justifyContent:"space-around",       alignItems:"center",     } });

Процесс написания и отладки-ощущения

Приложение создавалось мною с помощью Expo quickstart. Она создает базовое приложение, имеющее все основные разделы и базовый файл App.js, который предполагается изменять для получения желаемого результата. Проблем с конфигурацией практически нет — в этом большой плюс, т.к. попытки разобраться с созданием мобильных приложений на Java разбивались в том числе и о сложности конфигурации (это была не единственная причина, но с конфигами Джавы у меня исторически сложились напряженные отношения). Любое изменение практически сразу же можно увидеть на web-сервере, запускаемым Expo, либо — в эмуляторе.

Однако тут есть несколько проблем. Большую часть времени я отлаживал приложение в браузере, запуская эмулятор андроида только перед тем как отправить на сборку релизной версии. И — возникла очень не очевидная проблема, когда один и тот же код корректно работал на веб-версии и напрочь вылетал на андроиде, не оставляя никаких логов. Методом последовательного исключения компонентов было выявлено, что проблема в компоненте Picker, который ожидал получить в качестве называния строку, а получал — число. (данный элемент не попал в релизную версию программы, но не отметить я это не мог). Кроме того, в веб-версии шрифты можно было изменять с помощью поля строкового типа, задавая ему габариты в условно любых единицах — процентах, пикселях или даже архаичных «сантиметрах», в то время как на андроиде программа требовала дать ей только число.

Очень не порадовало отсутствие по умолчанию наследования свойств — нельзя было задать шрифт и выравнивание для корневого элемента, полагая, что все его потомки будут его поддерживать.
С другой стороны — очень к месту приходится возможность flex разметки, которая в вебе появилась сравнительно недавно — именно с ее помощью получается получать довольно аккуратную верстку даже на довольно экстремальных соотношениях сторон. С помощью «классического» CSS, безусловно, можно сделать то же самое, но это потребует на порядок более кропотливой работы.

Ну и безусловно — размер сборки получается слишком весомый. Я понимаю, что это плата за простоту написания, и на чем-либо уровня С, размер приложения будет существенно меньше, но тем не менее…

Выводы

В целом, учитывая опыт верстки на чистом Реакте, освоение основных принципов React-native заняло очень небольшое количество времени. Инструмент содержит в себе основные преимущества Реакта, однако — не клонирует его полностью, внося ряд ограничений и добавляя новые возможности. В некотором роде можно сказать, что нативный Реакт более требователен к типам, чем обычный, но если сравнивать его с React over TypeScript — различий становится еще меньше. Как мне кажется, можно сказать, что react-native может стать неплохой точкой входа в индустрию разработки мобильных приложений для лиц, имеющих опыт в вебе.

P.S. Cтатья не претендует на рассмотрение как авторитетное мнение о технологии, она лишь описывает — надеюсь, объективно — первое впечатление от инструмента от человека, имеющего опыт в смежной области.

P.P.S. Целевая аудитория статьи — не гуру веб разработки, а скорее смежники, подобные мне.

Исходники

ссылка на оригинал статьи https://habr.com/ru/post/537770/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *