Создаем калькулятор-конвертер на базе React. Часть 6: Добавляем конвертер

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

Предыдущий материал: Калькулятор-конвертер на базе React. Часть 5: Добавляем историю результатов

Создаем меню для выбора типа интерфейса

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

Сначала загрузим коллекцию иконок в стиле Чакры, чтобы добавить значок «Бургер» меню в качестве кнопки для переключения в другие режимы. Для этого откроем терминал в корневой директории проекта (если вы работаете в VS Code, то достаточно нажать на сочетание клавиш Ctrl + `). В командную строку вводим команду для установки иконок Chakra.

npm install @chakra-ui/icons

Теперь мы можем экспортировать иконки в Реакт-компоненты.

Импорт иконки HambuergIcon из ChakraUi
  • В файле App.jsx создаем импорт иконки «Бургера».
    import { HamburgerIcon } from '@chakra-ui/icons'
  • Затем вставляем эту иконку в функцию App, чтобы она отобразилась в интерфейсе приложения (стили можете не применять, я увеличил размер иконки, потому что по умолчанию она слишком мелкая).
    <HamburgerIcon />

Иконка есть, и теперь нам нужно меню для переключения между конвертером и калькулятором. Для этого создадим функцию Menu в файле App.jsx, а внутрь поместим примитивный блок Box, в котором будет размещаться интерфейс меню.

Компонент Menu

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

import { ListItem, UnorderedList } from '@chakra-ui/react'
Импорт элементов для создания списков из Chakra

Из вновь добавленных компонентов соберем неупорядоченный список из трех пунктов меню: CalculatorConverterSettings.

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

<UnorderedList>
 <ListItem>Calculator</ListItem>
 <ListItem>Converter</ListItem>
 <ListItem>Settings</ListItem>
</UnorderedList>
Верстка списка

Следующий этап – добавление анимации появления меню. По умолчанию оно отображается на постоянной основе, но нам нужно скрывать дополнительные опции до тех пор, пока они не понадобятся пользователю. В этом нам помогут компоненты SlideFade и useDisclosure из библиотеки Chakra. Импортируем их в свое приложение:

import { SlideFade, useDisclosure } from '@chakra-ui/react'
Импорт компонента SlideFade из ChakraUI

В компонент SlideFade затем нужно обернуть весь интерфейс меню. То есть все, что входит в блок Box в компоненте Menu.

Верстка меню с компонентом SlideFade

Далее нужно создать особое состояние (уникальное для интерфейса Chakra) useDisclosure в компоненте Menu, чтобы контролировать появление и исчезновение меню, замкнутое в SlideFade. Оно содержит в себе переменную и метод.

const {isOpen, onToggle} = useDisclosure()
Состояние useDisclosure в компоненте Menu

Теперь нужно привязать элемент isOpen в качестве атрибута для компонента SlideFade. Это необходимо, чтобы меню отображалось в интерфейсе только в случае задействования переменной isOpen.

<SlideFade in={isOpen}> </SlideFade>

А метод onToggle привязывает к иконке бургер-меню.

<HamburgerIcon onClick={onToggle} />
Код компонента Menu

Также добавим атрибут unmountOnExit в блок SlideFade, чтобы он демонтировался из дерева HTML-элементов и не был доступен в «невидимом» состоянии. В противном случае пользователь может случайно кликнуть на какой-то из пунктов настроек, хотя на экране этого блока вовсе нет.

Атрибут unmountOnExit

Само меню необходимо встроить в компонент App и поставить его над калькулятором.

Компонент Menu в компоненте App

Рекомендую к блоку Box в меню добавить атрибут position со значением absolute, чтобы список пунктов в SlideFade появлялся на экране и не сдвигал параллельно другие части интерфейса.

Свойство absolute у элемента Box

Также, возможно, лучше использовать такую вещь, как unstyled list, то есть вместо UnorderedList импортировать в файл List и обернуть меню именно в него. Тогда из интерфейса исчезнут ненужные точки и элементы списка перестанут сдвигаться в сторону.

Дополненный интерфейс компонента Menu

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

Комьюнити теперь в Телеграм
Подпишитесь и будьте в курсе последних IT-новостей

Подписаться

Делаем переключатель режимов работы приложения

Логика для нашего меню может быть похожа на ту, что мы использовали для переключения между InputCalc и ClickCalc. Так и сделаем – будем менять состояние компонента App и на его основании выбирать, какой из интерфейсов отрисовывать на экране.

Для этого создаем состояние mode внутри функции App.

const [mode, setMode] = useState('Calculator')

Здесь же объявляем переменную application для хранения компонента, отображаемого в текущий момент времени.

Состояние mode в компоненте App

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

function changeAppType () { mode == 'Calculator' ? setMode('Converter') : setMode('Calculator') }

Как видите, логика повторяется – проверяем активный тип компонента в текущий момент и заменяем его, если он не соответствует требованиям пользователя.

Функция смены типа приложения

Добавляем конструкцию switch. Точнее копируем ее из Calculator.jsx, заменяя только используемые переменные. Например, application вместо calculator и Converter вместо ClickCalc.

Конструкция Switch для переключения приложений

Заменяем компонент <Calculator /> на переменную {application}. А вот в компонент <Menu /> нужно будет пробросить пропс с функцией changeAppType.

<Menu onClick={changeAppType} />
Переменная application в компоненте App

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

Исправленная конструкция Switch

Конструкцию switch мы оставим прежней. В качестве пропса в компонент Menu будем передавать функцию setMode. То есть из меню мы будем напрямую влиять на состояние компонента App, а не проверять, каково сейчас состояние.

Заменим элементы списка на кнопки Button и к каждой кнопке привяжем функцию замены состояния компонента App.

  • Первая будет устанавливать значение на Calculator.
    <Button onClick={() => {props.onClick('Calculator')}}> Calculator </Button>
  • А вторая будет менять состояние на Converter.
    <Button onClick={() => {props.onClick('Converter')}}> Converter </Button>
Переключатели режимов

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

Реализуем функциональность конвертера

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

Для начала создадим базовый элемент Select, в котором можно будет выбрать один из видов метрических единиц. Но перед этим нужно импортировать соответствующий тип блоков из библиотеки Chakra UI. import { Select } from '@chakra-ui/react'

Импорт компонентов в конвертер

Теперь можно «сверстать» базовый вариант элемента Select. Он представляет собой выпадающий список с двумя значениями: Centimeters и Meters. Оба нужно подписать, и получится следующий код:

Select c разными типами метрик
  • Для сантиметров:
    <option value="Centimeters">Centimeters</option>
  • Для метров:
    <option value="Meters">Meters</option>

Это код необходимо продублировать, так как выбирать единицы измерения мы будем сразу в двух частях интерфейса. Будем выбирать, что конвертировать и во что. Для этого создадим функцию-компонент Converter и оттуда вернем Flex-блок с двумя Select.

Верстка блока с конвертером

В компанию к блокам Select добавим Input. В него мы будем вводить значение, которое будет конвертироваться в обратную сторону. Укажем для него числовое значение, чтобы нельзя было вводить буквы.

<Input type="number">
Input в компоненте Converter

Ниже добавим кнопку, запускающую процесс конвертации. Она будет актировать специальную функцию, отвечающую и за конвертацию, и за отображение полученного результата в интерфейсе. В нашем случае достаточно простой кнопки с подписью Convert.

<Button onClick={() => {convert()}}>Convert</Button>
Select-блок с референсами

Теперь немного подготовительных мер перед тем, как реализовать непосредственно логику конвертации. Создадим два состояния: одно будет отвечать за значение поля Input, а второе будет хранить в себе результат конвертации.

Состояния компонента Converter
const [input, setInput] = useState(0)

const [result, setResult] = useState(0)

Также нам нужно создать два референта для прямого обращения к элементам Select в интерфейсе конвертера.

Референсы в компоненте Converter

Пока назовем их просто первым и вторым:

const first = useRef()

const second = useRed()

useRed – это функция-хук, позволяющая взаимодействовать с HTML-элементами в рамках одного Реакт-компонента. Не нужно искать его вручную, используя метод querySelector. У вас появляется что-то в духе конкретного имени для HTML-элемента.

Далее нам нужно создать функцию изменения значения input, а вернее привязать изменение состояния input при вводе символов в строку Input. Для этого добавим соответствующий атрибут к подходящему HTML-элементу.

<Input onChange={(e) => {setInput(e.target.value)}} />
Функция setInput в блоке Converter

Тут же «прицепим» первый референс. Для этого надо найти блок с необходимыми данными и указать в качестве атрибута ref название референса, созданного внутри компонента. Например, в нашем случае один Select получит референс first.

<Select ref={first}

Это дает нам возможность получать информацию о выбранной единице измерения, указывая переменную first или second в теле методов, используемых в рамках функции Converter.

Мы создаем метод convert. Этот метод сначала проверяет, какая единица измерения установлена в первом Select. Если это метры, то метод переключается на формулы, разработанные для этой единицы измерения. В нашем случае задействуется конструкция switch, подбирающая формулу в соответствии с тем, какая единица измерения указана во втором референсе. Вот весь код по пунктам.

Функция convert
  • Объявляем функцию convert:
    function convert() { }
  • Делаем проверку первого референса на соответствие значению Meters. Для этого обратимся к элементу current и его значению value.
    if (first.current.value == 'Meters') { }
  • В теле проверки при ее успешном выполнении в ход вступает конструкция switch, она берет за основу значение второго референса.
    switch(second.current.value) { }
  • Внутри конструкции switch происходит перебор всех возможных значений и формулы для них. У нас пока только два случая: Centimeters и Meters. Для сантиметров указывается формула умножения значения метров на 100.
    case 'Centimeters': setResult(input * 100); break
  • А в случае с метрами и вовсе не нужно ничего менять.
    case 'Meters': setResult(input * 100); break
Формулы для расчета расстояний

Этот же код мы повторим и для сантиметров. То есть создаем проверку на соответствие первого референса значению Centimeters, потом снова включаем конструкцию switch, но уже с формулами для сантиметров. Например, для конвертации сантиметров в метры надо сделать case, делящий input на сотню.

case 'Meters': setResult(input / 100); break

Вместо заключения

Базовый конвертер готов. Теперь достаточно лишь добавлять новые формулы и пополнять элементы интерфейса новыми значениями. Рекомендую выносить формулы в другие функции или компоненты, чтобы хранить их в отдельном файле. Здесь они могут отнимать слишком много пространства, мешая восприятию кода.

Если возникнут какие-то вопросы, оставляйте комментарии. Далее создадим более продвинутый конвертер, черпающий данные из сети.


Related Posts