В этом руководстве мы создадим приложение React Calculator. Вы узнаете, как создавать каркас, разрабатывать макет, создавать компоненты, обновлять состояния и форматировать выходные данные.
- Планирование
- Цвета дизайна
- Настройка проекта
- Создать компоненты
- Wrapper
- Wrapper.js
- Wrapper.css
- Screen
- ButtonBox
- ButtonBox.js
- ButtonBox.css
- Button
- Button.js
- Button.css
- Элементы рендеринга
- Определить состояния
- Функциональность
- numClickHandler
- commaClickHandler
- signClickHandler
- equalsClickHandler
- invertClickHandler
- percentClickHandler
- resetClickHandler
- Форматирование ввода
- Собираем все вместе
- Заключительные примечания
Планирование
Поскольку мы будем создавать приложение «Калькулятор», давайте выберем область, которая не слишком сложна для изучения, но также не слишком проста для покрытия различных аспектов создания приложения.
Мы реализуем следующие функции:
- сложить, вычесть, умножить, разделить
- поддержка десятичных значений
- рассчитать проценты
- инвертировать значения
- сбросить функциональность
- форматировать большие числа
- изменение размера вывода в зависимости от длины
Для начала мы нарисуем базовый каркас для отображения наших идей. Для этого вы можете использовать бесплатные инструменты, такие как Figma или Diagrams.net .
Обратите внимание, что на этом этапе не так важно думать о цветах и стиле. Важнее всего то, что вы можете структурировать макет и идентифицировать задействованные компоненты.
Цвета дизайна
После того, как мы разобрались с макетом и компонентами, все, что останется сделать для завершения дизайна, — это выбрать красивую цветовую схему.
Ниже приведены некоторые рекомендации по улучшению внешнего вида приложения:
- обертка должна контрастировать с фоном
- значения экрана и кнопок должны быть легко читаемыми
- кнопка равенства должна быть другого цвета, чтобы сделать акцент
Основываясь на приведенных выше критериях, мы будем использовать цветовую схему, показанную ниже.
Настройка проекта
Для начала откройте терминал в папке проектов и создайте шаблонный шаблон с помощью приложения create-response-app . Для этого выполните команду:
npx create-react-app calculator
Это самый быстрый и простой способ настроить полностью работающее приложение React с нулевой конфигурацией. Все, что вам нужно сделать после этого, — это запустить, cd calculatorчтобы переключиться во вновь созданную папку проекта и npm startзапустить приложение в браузере.
Как видите, он поставляется с некоторым шаблоном по умолчанию, поэтому теперь мы немного очистим дерево папок проекта.
Найдите srcпапку, в которой будет жить логика вашего приложения, и удалите все, кроме App.jsсоздания вашего приложения, index.cssстилизации вашего приложения и index.jsвизуализации вашего приложения в DOM.
Создать компоненты
Поскольку мы уже сделали каркасную модель, мы уже знаем основные строительные блоки приложения. Те Wrapper, Screen, ButtonBox, и Button.
Сначала создайте componentsпапку внутри srcпапки. Затем мы создадим отдельный .jsфайл и .cssфайл для каждого компонента.
Если вы не хотите создавать эти папки и файлы вручную, вы можете использовать следующий однострочный файл для быстрой настройки:
cd src && mkdir components && cd components && touch Wrapper.js Wrapper.css Screen.js Screen.css ButtonBox.js ButtonBox.css Button.js Button.css
Wrapper
WrapperКомпонент будет фрейм, содержащий все компоненты детей месте. Это также позволит нам впоследствии центрировать все приложение.
Wrapper.js
import "./Wrapper.css"; const Wrapper = ({ children }) => { return <div className="wrapper">{children}</div>; }; export default Wrapper;
Wrapper.css
.wrapper { width: 340px; height: 540px; padding: 10px; border-radius: 10px; background-color: #485461; background-image: linear-gradient(315deg, #485461 0%, #28313b 74%); }
Screen
Screen Компонент будет верхняя часть потомком Wrapperкомпонента, и его цель будет отображать вычисленные значения.
В список функций мы включили изменение размера вывода на экран по длине, что означает, что более длинные значения должны уменьшаться в размере. Для этого мы воспользуемся небольшой (3,4 КБ gzip) библиотекой response-textfit .
Чтобы установить его, запустите, npm i react-textfitа затем импортируйте и используйте, как показано ниже.
Screen.js
import { Textfit } from "react-textfit"; import "./Screen.css"; const Screen = ({ value }) => { return ( <Textfit className="screen" mode="single" max={70}> {value} </Textfit> ); }; export default Screen;
Screen.css
.screen { height: 100px; width: 100%; margin-bottom: 10px; padding: 0 10px; background-color: #4357692d; border-radius: 10px; display: flex; align-items: center; justify-content: flex-end; color: white; font-weight: bold; box-sizing: border-box; }
ButtonBox
ButtonBoxКомпонент, аналогична Wrapperкомпонента будет рамка для детей — только на этот раз для Buttonкомпонентов.
ButtonBox.js
import "./ButtonBox.css"; const ButtonBox = ({ children }) => { return <div className="buttonBox">{children}</div>; }; export default ButtonBox;
ButtonBox.css
.buttonBox { width: 100%; height: calc(100% - 110px); display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(5, 1fr); grid-gap: 10px; }
Button
Button Компонент обеспечит интерактивность приложения. Каждый компонент будет иметь valueи onClickреквизит.
В таблицу стилей мы также включим стили для equalкнопки. ButtonПозже мы будем использовать props для доступа к классу.
Button.js
import "./Button.css"; const Button = ({ className, value, onClick }) => { return ( <button className={className} onClick={onClick}> {value} </button> ); }; export default Button;
Button.css
button { border: none; background-color: rgb(80, 60, 209); font-size: 24px; color: rgb(255, 255, 255); font-weight: bold; cursor: pointer; border-radius: 10px; outline: none; } button:hover { background-color: rgb(61, 43, 184); } .equals { grid-column: 3 / 5; background-color: rgb(243, 61, 29); } .equals:hover { background-color: rgb(228, 39, 15); }
Элементы рендеринга
Базовый файл для рендеринга в приложениях React — это index.js. Прежде чем мы продолжим, убедитесь, что вы index.jsвыглядите следующим образом:
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import "./index.css"; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById("root") );
Кроме того, давайте проверим index.cssи убедимся, что мы сбросили значения по умолчанию для paddingи margin, выберем какой-нибудь отличный шрифт (например, Montserrat в данном случае) и установим правильные правила для центрирования приложения в области просмотра:
@import url("https://fonts.googleapis.com/css2?family=Montserrat&display=swap"); * { margin: 0; padding: 0; font-family: "Montserrat", sans-serif; } body { height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #fbb034; background-image: linear-gradient(315deg, #fbb034 0%, #ffdd00 74%); }
Наконец, давайте откроем основной файл App.jsи импортируем все компоненты, которые мы создали ранее:
import Wrapper from "./components/Wrapper"; import Screen from "./components/Screen"; import ButtonBox from "./components/ButtonBox"; import Button from "./components/Button"; const App = () => { return ( <Wrapper> <Screen value="0" /> <ButtonBox> <Button className="" value="0" onClick={() => { console.log("Button clicked!"); }} /> </ButtonBox> </Wrapper> ); }; export default App;
В приведенном выше примере мы визуализировали только один Buttonкомпонент.
Давайте создадим представление данных в виде массива в каркасе, чтобы мы могли отображать и отображать все кнопки в ButtonBox:
import Wrapper from "./components/Wrapper"; import Screen from "./components/Screen"; import ButtonBox from "./components/ButtonBox"; import Button from "./components/Button"; const btnValues = [ ["C", "+-", "%", "/"], [7, 8, 9, "X"], [4, 5, 6, "-"], [1, 2, 3, "+"], [0, ".", "="], ]; const App = () => { return ( <Wrapper> <Screen value=0 /> <ButtonBox> { btnValues.flat().map((btn, i) => { return ( <Button key={i} className={btn === "=" ? "equals" : ""} value={btn} onClick={() => { console.log(`${btn} clicked!`); }} /> ); }) } </ButtonBox> </Wrapper> ); };
Проверьте свой терминал и убедитесь, что ваше приложение React все еще работает. Если нет, бегите, npm startчтобы запустить его снова.
Откройте ваш браузер. Если вы продолжили, ваш текущий результат должен выглядеть так:
Если вы хотите, вы также можете открыть инструменты разработчика браузера и проверить значения журнала для каждой нажатой кнопки.
Определить состояния
Затем мы объявим переменные состояния с помощью useStateперехватчика React .
В частности, будет три состояния:, numвведенное значение; sign, выбранный знак: и res, рассчитанное значение.
Чтобы использовать useStateловушку, мы должны сначала импортировать ее в App.js:
import React, { useState } from "react";
В Appфункции мы будем использовать объект для установки всех состояний сразу:
import React, { useState } from "react"; // ... const App = () => { let [calc, setCalc] = useState({ sign: "", num: 0, res: 0, }); return ( // ... ); };
Функциональность
Наше приложение выглядит неплохо, но не работает. В настоящее время он может выводить только значения кнопок в консоль браузера. Давайте это исправим!
Начнем с Screenкомпонента. Задайте следующую условную логику для valueprop, чтобы отображалось введенное число (если число введено) или вычисленный результат (если нажата кнопка равенства).
Для этого мы будем использовать встроенный тернарный оператор JS , который, по сути, является сокращением для ifоператора, принимая выражение и возвращая значение после, ?если выражение истинно, или после, :если выражение ложно:
<Screen value={calc.num ? calc.num : calc.res} />
Теперь давайте отредактируем Buttonкомпонент, чтобы он мог обнаруживать различные типы кнопок и выполнять назначенную функцию после нажатия определенной кнопки. Используйте приведенный ниже код:
import React, { useState } from "react"; // ... const App = () => { // ... return ( <Wrapper> <Screen value={calc.num ? calc.num : calc.res} /> <ButtonBox> {btnValues.flat().map((btn, i) => { return ( <Button key={i} className={btn === "=" ? "equals" : ""} value={btn} onClick={ btn === "C" ? resetClickHandler : btn === "+-" ? invertClickHandler : btn === "%" ? percentClickHandler : btn === "=" ? equalsClickHandler : btn === "/" || btn === "X" || btn === "-" || btn === "+" ? signClickHandler : btn === "." ? commaClickHandler : numClickHandler } /> ); })} </ButtonBox> </Wrapper> ); };
Теперь мы готовы создать все необходимые функции.
numClickHandler
numClickHandlerФункция срабатывает только тогда , когда какой — либо из цифровых кнопок (0-9) прессуют. Затем он получает значение Buttonи добавляет его к текущему numзначению.
Он также обеспечит следующее:
- целые числа не начинаются с нуля
- перед запятой нет нескольких нулей
- формат будет «0». если «.» нажимается первым
- числа вводятся длиной до 16 целых чисел
import React, { useState } from "react"; // ... const App = () => { // ... const numClickHandler = (e) => { e.preventDefault(); const value = e.target.innerHTML; if (calc.num.length < 16) { setCalc({ ...calc, num: calc.num === 0 && value === "0" ? "0" : calc.num % 1 === 0 ? Number(calc.num + value) : calc.num + value, res: !calc.sign ? 0 : calc.res, }); } }; return ( // ... ); };
commaClickHandler
commaClickHandlerФункция увольняют только тогда , когда десятичная точка ( .) нажата. Он добавляет десятичную точку к текущему numзначению, делая его десятичным числом.
Это также обеспечит невозможность использования нескольких десятичных знаков.
Примечание. Я назвал функцию обработки «commaClickHandler», потому что во многих частях мира целые и десятичные числа разделяются запятой, а не десятичной точкой.
// numClickHandler function const commaClickHandler = (e) => { e.preventDefault(); const value = e.target.innerHTML; setCalc({ ...calc, num: !calc.num.toString().includes(".") ? calc.num + value : calc.num, }); };
signClickHandler
signClickHandlerФункция получает срабатывает , когда пользователь нажмет либо + , — , * или / . Затем конкретное значение устанавливается как текущее signзначение в calcобъекте.
Это также обеспечит отсутствие эффекта на повторные вызовы:
// commaClickHandler function const signClickHandler = (e) => { e.preventDefault(); const value = e.target.innerHTML; setCalc({ ...calc, sign: value, res: !calc.res && calc.num ? calc.num : calc.res, num: 0, }); };
equalsClickHandler
equalsClickHandlerФункция вычисляет результат , когда кнопка равно ( = ) нажата. Расчет основан на текущем numи resзначении, а также на signвыбранном (см. mathФункцию).
Возвращенное значение затем устанавливается как новое resдля дальнейших вычислений.
Он также обеспечит следующее:
- нет эффекта на повторные звонки
- пользователи не могут разделить на 0
// signClickHandler function const equalsClickHandler = () => { if (calc.sign && calc.num) { const math = (a, b, sign) => sign === "+" ? a + b : sign === "-" ? a - b : sign === "X" ? a * b : a / b; setCalc({ ...calc, res: calc.num === "0" && calc.sign === "/" ? "Can't divide with 0" : math(Number(calc.res), Number(calc.num), calc.sign), sign: "", num: 0, }); } };
invertClickHandler
invertClickHandlerФункция сначала проверяет, есть ли введенное значение ( num) или расчетное значение ( res) , а затем инвертирует их путем умножения с −1:
// equalsClickHandler function const invertClickHandler = () => { setCalc({ ...calc, num: calc.num ? calc.num * -1 : 0, res: calc.res ? calc.res * -1 : 0, sign: "", }); };
percentClickHandler
В percentClickHandlerФункции проверяет, есть ли введенное значение ( num) или расчетное значение ( res) , а затем вычисляет процент , используя встроенный в Math.powфункции, которая возвращает базу в степень:
// invertClickHandler function const percentClickHandler = () => { let num = calc.num ? parseFloat(calc.num) : 0; let res = calc.res ? parseFloat(calc.res) : 0; setCalc({ ...calc, num: (num /= Math.pow(100, 1)), res: (res /= Math.pow(100, 1)), sign: "", }); };
resetClickHandler
Эти resetClickHandlerфункции по умолчанию всех начальных значений calc, возвращая calcсостояние , как это было , когда приложение Калькулятора было сначала сделано:
// percentClickHandler function const resetClickHandler = () => { setCalc({ ...calc, sign: "", num: 0, res: 0, }); };
Форматирование ввода
И последнее, что нужно сделать для завершения списка функций во вступлении, — это реализовать форматирование значений. Для этого мы могли бы использовать измененную строку Regex, отправленную Эмиссаром :
const toLocaleString = (num) => String(num).replace(/(?<!\..*)(\d)(?=(?:\d{3})+(?:\.|$))/g, "$1 ");
По сути, он берет число, форматирует его в строковый формат и создает разделители пробелов для знака тысяч. Если мы обратим процесс и хотим обработать строку чисел, сначала нам нужно удалить пробелы, чтобы позже мы могли преобразовать ее в число. Для этого вы можете использовать эту функцию:
const removeSpaces = (num) => num.toString().replace(/\s/g, "");
Вот код, в который вы должны включить обе функции:
import React, { useState } from "react"; // ... const toLocaleString = (num) => String(num).replace(/(?<!\..*)(\d)(?=(?:\d{3})+(?:\.|$))/g, "$1 "); const removeSpaces = (num) => num.toString().replace(/\s/g, ""); const App = () => { // ... return ( // ... ); };
Ознакомьтесь со следующим разделом с полным кодом о том, как добавить toLocaleStringи removeSpacesобработать функции для Buttonкомпонента.
Собираем все вместе
Если вы следовали инструкциям, весь App.jsкод должен выглядеть так:
import React, { useState } from "react"; import Wrapper from "./components/Wrapper"; import Screen from "./components/Screen"; import ButtonBox from "./components/ButtonBox"; import Button from "./components/Button"; const btnValues = [ ["C", "+-", "%", "/"], [7, 8, 9, "X"], [4, 5, 6, "-"], [1, 2, 3, "+"], [0, ".", "="], ]; const toLocaleString = (num) => String(num).replace(/(?<!\..*)(\d)(?=(?:\d{3})+(?:\.|$))/g, "$1 "); const removeSpaces = (num) => num.toString().replace(/\s/g, ""); const App = () => { let [calc, setCalc] = useState({ sign: "", num: 0, res: 0, }); const numClickHandler = (e) => { e.preventDefault(); const value = e.target.innerHTML; if (removeSpaces(calc.num).length < 16) { setCalc({ ...calc, num: calc.num === 0 && value === "0" ? "0" : removeSpaces(calc.num) % 1 === 0 ? toLocaleString(Number(removeSpaces(calc.num + value))) : toLocaleString(calc.num + value), res: !calc.sign ? 0 : calc.res, }); } }; const commaClickHandler = (e) => { e.preventDefault(); const value = e.target.innerHTML; setCalc({ ...calc, num: !calc.num.toString().includes(".") ? calc.num + value : calc.num, }); }; const signClickHandler = (e) => { e.preventDefault(); const value = e.target.innerHTML; setCalc({ ...calc, sign: value, res: !calc.res && calc.num ? calc.num : calc.res, num: 0, }); }; const equalsClickHandler = () => { if (calc.sign && calc.num) { const math = (a, b, sign) => sign === "+" ? a + b : sign === "-" ? a - b : sign === "X" ? a * b : a / b; setCalc({ ...calc, res: calc.num === "0" && calc.sign === "/" ? "Can't divide with 0" : toLocaleString( math( Number(removeSpaces(calc.res)), Number(removeSpaces(calc.num)), calc.sign ) ), sign: "", num: 0, }); } }; const invertClickHandler = () => { setCalc({ ...calc, num: calc.num ? toLocaleString(removeSpaces(calc.num) * -1) : 0, res: calc.res ? toLocaleString(removeSpaces(calc.res) * -1) : 0, sign: "", }); }; const percentClickHandler = () => { let num = calc.num ? parseFloat(removeSpaces(calc.num)) : 0; let res = calc.res ? parseFloat(removeSpaces(calc.res)) : 0; setCalc({ ...calc, num: (num /= Math.pow(100, 1)), res: (res /= Math.pow(100, 1)), sign: "", }); }; const resetClickHandler = () => { setCalc({ ...calc, sign: "", num: 0, res: 0, }); }; return ( <Wrapper> <Screen value={calc.num ? calc.num : calc.res} /> <ButtonBox> {btnValues.flat().map((btn, i) => { return ( <Button key={i} className={btn === "=" ? "equals" : ""} value={btn} onClick={ btn === "C" ? resetClickHandler : btn === "+-" ? invertClickHandler : btn === "%" ? percentClickHandler : btn === "=" ? equalsClickHandler : btn === "/" || btn === "X" || btn === "-" || btn === "+" ? signClickHandler : btn === "." ? commaClickHandler : numClickHandler } /> ); })} </ButtonBox> </Wrapper> ); }; export default App;
Заключительные примечания
Поздравляю! Вы создали полностью функциональное и стильное приложение. Надеюсь, вы узнали кое-что в процессе!
Некоторые дальнейшие идеи, которые вы можете изучить, — это добавить некоторые научные функции или реализовать память со списком предыдущих вычислений.