Очередной этап разработки конвертера-калькулятора на базе Реакта. В этом уроке создадим компонент для сохранения всех результатов вычисления, выполненных за время использования калькулятора.
Предыдущий материал: Калькулятор-конвертер на базе React. Часть 4: Декомпозиция и мелкие исправления
Цели задачи
Мотивация за созданием подобной функции следующая: в большинстве мобильных и дескоптных калькуляторов нет нормального механизма для просмотра предыдущих результатов расчетов. То есть если посчитали что-то, а потом посчитали что-то еще, то предыдущий результат будет заменен новым и вернуться к нему не получится. Я не один десяток раз натыкался на сценарии, когда было бы удобно иметь под рукой результат вычисления, выполненного несколько минут назад. И очень раздражает, что нельзя просто взять уже готовый расчет из того же интерфейса и воспользоваться им в новых подсчетах. Ну или хотя бы иметь возможность вручную переписать существующее значение в новый пример.
Справедливости ради стоит отметить, что такая функциональность есть в некоторых приложениях (Calzy, например). Но это большая редкость, поэтому мы сделаем свой вариант.
Создаем компонент History
Начнем с создания специальной функции History, которая будет содержать в себе все элементы с результатами предыдущих расчетов и отображать их в интерфейсе приложения прямо над строкой ввода значений.
Сначала объявляем функцию History в компоненте Calculator (именно в нем, потому что мы хотим, чтобы история была доступна как в визуальном режиме расчетов, так и в смарт-режиме, а в перспективе туда можно сохранять еще и результаты вычислений, связанных с конвертацией).
В коде функция будет выглядеть так:
function History(props) { }
Теперь переходим в компонент Calculator и создаем внутри него отдельное состояние для хранения массива со всеми предыдущими вычислениями. Значением по умолчанию тоже будет массив, но пустой, чтобы не возникало ошибок при добавлении новых элементов.
const [history, setHistory] = useState([])
Полученное состояние мы сможем передать в компонент History, который мы добавим в Box рядом с переменной calculator, отвечающей за размещение интерфейса калькулятора в нашей программе.
<Box> <History data={history} /> </Box>
Переданные данные можно использовать для рендеринга списка элементов в истории. Снова возвращаемся к компоненту History и внедряем в него переменную results, которая будет пробегаться по списку пропсов, переданных из родительской функции, и возвращать кнопки со значением последнего расчета в качестве внутреннего текста.
const results = props.data.map(result => { return <Button key={result}>{result}</Button> })}
Из компонента History нужно вернуть весь массив с вычислениями, что там хранится. То есть переменную results, сформированную выше.
return (<Box> {result} </Box>)
Готово. Только вот история у нас пустая и пока что в интерфейсе ничего не отображается. Далее мы это изменим.
Добавляем колбэки для обновления элементов в компоненте History
Теперь нам нужна дополнительная функция в компоненте Calculator, отвечающая за переопределение переменной history и размещение внутри нее обновленного состояния истории (при появлении новых расчетов, естественно).
-
Переходим к компоненту Calculator и объявляем там функцию updateHistory с аргументом calcResult.
function updateHistory(calcResult) { }
-
В теле функции переназначаем history на обновленный массив, объединенный с существующим массивом, который производит расчет текущего примера при помощи функции eval.
setHistory(history.concat(eval(calcResult)))
Аргументом calcResult будет текущее состояние расчетов в одном из компонентов калькулятора (InputCalc или ClickCalc).
Далее необходимо реализовать колбэк, то есть метод, обращающийся к другому компоненту Реакта и возвращающий значение для запуска какой-либо логики в компоненте, который выполнил запрос. Проще говоря, передадим функцию через пропсы, чтобы задействовать updateHistory внутри Calculator, но из InputCalc или ClickCalc.
Для этого отправим в ClickCalc пропс с названием onClick.
<ClickCalc onClick={updateHistory} />
Теперь внутри ClickCalc нужно дописать аргумент props, так как раньше его в нашем приложении не существовало.
И метод updateHistory привяжем к кнопке «Равно». Мне такой подход кажется наиболее логичным. С помощью него мы получаем нужный результат и тут же отправляем его в историю вычислений.
props.onClick(counts)
А вот с InputCalc немного сложнее. Передать туда функцию updateHistory можно таким же способом. Просто укажем в качестве пропса:
<InpurCalc onKeyDown={updateHistory} />
Мы используем пропс onKeyDown, потому что функция должна срабатывать при нажатии на клавишу, а не при клике мышью. По задумке окончательный расчет и перемещение результата в историю должны происходить по нажатию на клавишу Enter. Как по мне, вполне логичный вариант.
Поэтому находим внутри компонента InputCalc элемент Input, отвечающий за отображение символов в строке подсчетов. Добавляем к нему функцию sendDataToHistory (которую мы создадим чуть позже), срабатывающую по нажатию на клавиатуре. Получится такой код:
<Input onKeyDown={(e) => {sendDataToHistory}}
Сейчас наш код не годится, потому что он будет реагировать на нажатие любой кнопки на клавиатуре. Мы должно исключить все клавиши, кроме Enter, поэтому внутри функции sendDataToHistory реализуем проверку нажимаемой кнопки на соответствие нужной нам. И если проверка завершится успешно, то сработает метод updateHistory из родительского компонента с переменной counts в качестве аргумента.
function sendDataToHistory(e) { if (e.nativeEvent.key == "Enter") { props.onKeyDown(counts) } }
Готово. Теперь по нажатию на Enter будет срабатывать вычисление и моментальная отправка данных в History. Останется лишь прицепить к этому методу еще и зачистку строки расчетов, если вы хотите каждый раз начинать сначала по нажатию на Enter. Для этого в функцию sendDataToHistory надо добавить метод setCount, обнуляющий значение состояния компонента.
setCounts('')
Также стоит ограничить количество отображаемых в истории элементов. Пока что сократим их число до 7. Но мы не просто не позволим добавлять новые вычисления, а будем удалять наименее актуальные. В дальнейшем можно будет создать отдельное меню, где будет отображаться расширенная история.
Добавим в метод updateHistory проверку количества элементов в массиве history.
if (history.length > 6) {history.shift()}
Вместо заключения
Теперь у нас есть список с историей всех вычислений. В дальнейшим мы его дополним и сделаем возможность быстро переиспользовать результаты подсчетов в новых примерах, чтобы не приходилось тратить время на ручной ввод нужных значений.
Продолжение: Калькулятор-конвертер на базе React. Часть 6: Добавляем конвертер
Комментарии