Продолжаем расширять функциональность калькулятора на базе React. На этот раз добавим возможность проводить расчеты в автоматическом режиме при вводе выражений напрямую в строку. Фактически мы повторим функциональность калькулятора поисковой строки в браузерах и Spotlight (глобальная поисковая система в macOS и iOS).
Расчет будет производиться функцией eval в соответствии с базовыми правилами математики.
Предыдущий материал: Калькулятор-конвертер на базе React. Часть 2: Базовый интерфейс
Добавляем новый компонент
За новый тип расчетов будет отвечать отдельная функция, обрисовывающая собственную часть интерфейса, а именно строку для ввода текста.
Поэтому сначала нам нужно импортировать в свое приложение новый элемент из Чакры – Input. Это кастомизируемое HTML-поле для ввода текста, цифр и другого контента.
import { Input } from '@chakra-ui/react'
После этого создадим компонент InputCalc, где и будет отображаться строка для ввода текста.
function InputCalc (props) { }
В теле новой функции нужно сразу задать состояние. Отдельное поле для подсчетов здесь не используется, поэтому в нашем случае будет только состояние result с результатом расчетов. По умолчанию оно должно представлять собой пустую строку.
const [result, setResult] = useState('')
И в конце функции не забываем вернуть строку для ввода текста.
return ( <Input type="text"> </Input> )
Функция подсчета результатов и вывод переменной result в интерфейс
Сам по себе калькулятор ничего считать не будет, поэтому нам нужна отдельная функция, которая будет производить вычитания, сложения и другие операции по мере ввода текста. Сразу запишем ее в поле Input.
onInput={ (e) => {updateCounts(e)}}
Эта запись обозначает, что при любом изменении текста в поле Input будет активироваться функция updateCounts (мы объявим ее чуть позже), а ее аргументом будет непосредственно событие input (ввод или удаление символов из соответствующей строки).
Также мы добавим блок Text, чтобы отобразить на экране результат вычислений – переменную result.
<Text> {result} </Text>
Следующий этап – создание функции updateCounts, подсчитывающей результат выражения в нашем Input.
function updateCount (e) { }
Тут мы сразу сталкиваемся с проблемой – если использовать eval при каждом onInput, то программа сломается после введения любого знака, непохожего на число. Нажав на +, вы спровоцируете критическую ошибку и все приложение рухнет. Поэтому в теле updateCounts нужно сразу прописать проверку на соответствие введенных символов «безопасным» знакам.
-
Создаем переменную expressions, где будет регулярное выражение с символами, которых необходимо избегать.
const expressions = /\+|\-|\/|\*|=|[A-z]| /
-
Объявляем переменную lastNumber, хранящую значение последнего введенного символа в Input-блоке (e.target здесь – это HTML-элемент, связанный с Input).
const lastNumber = e.target.value[e.target.value.length - 1]
-
Прописываем условие проверки. Если последний знак входит в лист запрещенных, то мы не производим расчет.
if (expressions.test(lastNumber)) return
-
А если не входит, то считаем результат.
else setResult(eval(e.target.value))
Можно пойти от обратного и проверять, является ли последний символ числом. Если нет, то работу программы блокируем, если является – производим расчет.
Получится такое выражение. Мы убрали все знаки, кроме числительных, и добавили закрывающуюся скобку. Она нужна, чтобы пользователь мог настраивать порядок расчетов (eval считает значения в скобках в первую очередь, а потом берется за остальные в зависимости от приоритета знаков).
Такой подход лучше (меньше кода, меньше шансов сломать программу). Но проблема в том, что наше приложение предпринимает попытку сделать расчет каждый раз, когда пользователь вводит число. Если до этого он ввел знак доллара или десять плюсов подряд, то расчет производиться не будет. Программа не «упадет», но будет сыпать десятками ошибок в консоль, что не очень хорошо.
В такой ситуации можно воспользоваться методикой перехвата контроля над формами в Реакте. Мы можем забрать у стандартных элементов возможность управлять содержимым форм и самостоятельно заменять значения в них. Это позволяет запретить ввод тех или иных символов. В нашем случае это как раз то, что нужно, чтобы пользователь программы не мог случайно ввести два плюса и начать генерировать ошибки.
Создаем новое состояние counts, где будет храниться информация о строке ввода для нашего калькулятора. Значением по умолчанию можно оставить пустую строчку.
const [counts, setCounts] = useState('')
Заменим значение value в Input со стандартного на то, что необходимо нам, а именно на значение нового состояния counts, которое мы добавили выше.
<Input value={counts} />
Используем ту же переменную expressions (можно дополнить ее и другими запрещенными знаками, если надо).
Корректируем переменную lastNumber, чтобы она показывала не последний символ в строке, а предпоследний (это необходимо, потому что в противном случае функция не даст указать запрещенный знак, даже если последнее значение в строке – это число).
const lastNumber = e.target.value[e.target.value.length - 2]
Создаем дополнительную проверку, чтобы убедиться в том, что мы вводим корректные символы и их можно смело добавлять в строку для расчета результата.
-
Сначала проверяем, не вводится ли повторно запрещенный знак. Такая многоэтапная проверка позволяет исключить практически любое неадекватное поведение калькулятора.
if (expressions.test(lastNumber) && expressions.test(e.nativeEvent.data) && e.nativeEvent.data != null) return
-
Потом убеждаемся в том, что в строке нет «опасных» знаков и можно смело производить расчет при помощи eval.
if (!expressions.test(e.nativeEvent.data)) setResult(eval(e.target.value))
-
А потом уже добавляем контент в строку.
setCounts(e.tager.value)
В этом случае тоже можно пойти от обратного и проверять вводимые символы на соответствие стандартам безопасности для подсчета в eval.
-
Заменяем expressions на числовые выражения.
const expressions = /[0-9]/
-
В первой условии подменяем проверку на true проверкой на false.
if (!expressions.test(lastNumber) && !expressions.test(e.nativeEvent.data) && e.nativeEvent.data != null) return
-
Во втором условии все наоборот.
if (expressions.test(e.nativeEvent.data)) setResult(eval(e.target.value))
-
Оставляем обновление поля Input.
setCounts(e.target.value)
Готово. Осталось добавить калькулятор в основной компонент, чтобы он отображался в интерфейсе. Просто вписываем <InputCalc /> в компонент App.
Вот как по итогу будет выглядеть код программы в return-блоке компонента App:
Получится подобный интерфейс. У нас в одной части приложения сразу отображаются два режима, что нам не очень подходит, и этот момент мы решим уже в следующей статье, когда будем заниматься разбиением программы на составные блоки в разных файлах.
Вместо заключения
Мы добавили первую умную функцию в наш калькулятор. Теперь необязательно тыкать по кнопкам, чтобы быстро посчитать выражение. Далее мы продолжим изучать Реакт, будем решать мелкие проблемы калькулятора и развивать его возможности.
Продолжение: Калькулятор-конвертер на базе React. Часть 4: Декомпозиция и мелкие исправления
Комментарии