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

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

<cut />

Введение

Ввиду того, что на момент планированного перехода в мобильную разработку основным инструментов разработки для меня являлся 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 Целевая аудитория статьи – не гуру веб разработки, а скорее смежники, подобные мне.

Исходники

Let’s block ads! (Why?)

Read More

Recent Posts

VK купила 40% билетной платформы Intickets.ru

VK объявляет о приобретении 40% компании Intickets.ru (Интикетс). Это облачный сервис для контроля и управления продажей билетов на мероприятия. Сумма…

2 дня ago

OpenAI готовится запустить поисковую систему на базе ChatGPT

OpenAI готовится запустить собственную поисковую систему на базе ChatGPT. Информацию об этом публикуют западные издания. Ожидается, что новый поисковик может…

2 дня ago

Роскомнадзор рекомендовал хостинг-провайдерам ограничить сбор данных с сайтов для иностранных ботов

Центр управления связью общего пользования (ЦМУ ССОП) Роскомнадзора рекомендовал компаниям из реестра провайдеров ограничить доступ поисковых ботов к информации на российских сайтах.…

3 дня ago

Apple возобновила переговоры с OpenAI и Google для интеграции ИИ в iPhone

Apple возобновила переговоры с OpenAI о возможности внедрения ИИ-технологий в iOS 18, на основе данной операционной системы будут работать новые…

1 неделя ago

Российская «дочка» Google подготовила 23 иска к крупнейшим игрокам рекламного рынка

Конкурсный управляющий российской «дочки» Google подготовил 23 иска к участникам рекламного рынка. Общая сумма исков составляет 16 млрд рублей –…

1 неделя ago

Google завершил обновление основного алгоритма March 2024 Core Update

Google завершил обновление основного алгоритма March 2024 Core Update. Раскатка обновлений была завершена 19 апреля, но сообщил об этом поисковик…

1 неделя ago