В этом уроке мы создадим приложение React Calculator. Вы узнаете, как создавать каркасы, разрабатывать макеты, создавать компоненты, обновлять состояния и форматировать выходные данные.
Чтобы вдохновить вас, вот ссылка на развернутый проект, который мы будем создавать.
Кроме того, вот исходный код, просто для справки, если вам понадобится помощь на любом этапе проекта.
Планирование
Поскольку мы будем создавать приложение «Калькулятор», давайте выберем область, которая не слишком сложна для изучения, но и не слишком проста для охвата различных аспектов создания приложения.
Функции, которые мы реализуем, включают:
складывать, вычитать, умножать, делить
поддержка десятичных значений
рассчитать проценты
инвертировать значения
сбросить функциональность
форматировать большие числа
выходное изменение размера в зависимости от длины
Для начала мы нарисуем базовый каркас для отображения наших идей. Для этого можно использовать бесплатные инструменты вроде Figma или Diagrams.net.
Каркас
Обратите внимание, что на этом этапе не так важно думать о цветах и стилях. Самое главное, что вы можете структурировать макет и определить задействованные компоненты.
Цвета дизайна
После того, как мы разобрались с макетом и компонентами, все, что осталось сделать для завершения дизайна, — это выбрать красивую цветовую схему.
Ниже приведены некоторые рекомендации, чтобы приложение выглядело великолепно:
обертка должна контрастировать с фоном
экран и значения кнопок должны быть легко читаемы
кнопка равенства должна быть другого цвета, чтобы придать некоторый акцент
Основываясь на вышеуказанных критериях, мы будем использовать цветовую схему, показанную ниже.
Цветовая схема
Настройка проекта
Для начала откройте терминал в папке ваших проектов и создайте стандартный шаблон с помощью
npx
Это самый быстрый и простой способ настроить полностью работающее приложение 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.js
import «. /Wrapper.css»;
const Wrapper = ({ children }) => {
return
;
};
export default Wrapper;
Wrapper.css
.wrapper {
width: 340px;
height: 540px;
padding: 10px;
}
Экран
Компонент Screenбудет верхней дочерней частью Wrapperкомпонента, и его целью будет отображение вычисленных значений.
В список функций мы включили изменение размера отображаемого вывода в зависимости от длины, что означает, что более длинные значения должны уменьшаться в размере. Для этого мы будем использовать небольшую (3,4 КБ gzip) библиотеку под названием
Чтобы установить его, запустите, npm i
Экран.js
import { Textfit } from «
import «. /Screen.css»;
const Screen = ({ value }) => {
return (
{value}
) ;
};
export default Screen;
Экран.css
.screen {
height: 100px;
width: 100%;
padding: 0 10px;
display: flex;
color: white;
}
КнопкаБокс
Компонент ButtonBox, как и Wrapperкомпонент, будет фреймом для дочерних элементов — только на этот раз для Buttonкомпонентов.
ButtonBox.js
import «. /ButtonBox.css»;
const ButtonBox = ({ children }) => {
return
;
};
export default ButtonBox;
ButtonBox.css
.buttonBox {
width: 100%;
height: calc (100% — 110px) ;
display: grid;
}
Кнопка
Компонент Buttonобеспечит интерактивность для приложения. Каждый компонент будет иметь свойства valueи onClick.
В таблицу стилей мы также включим стили для equalкнопки. Позже мы будем использовать Buttonсвойства для доступа к классу.
Кнопка.js
import «. /Button.css»;
const Button = ({ className, value, onClick }) => {
return (
) ;
};
export default Button;
Кнопка.css
button {
border: none;
color: rgb (255, 255, 255) ;
cursor: pointer;
outline: none;
}
button: hover {
}
.equals {
}
.equals: hover {
}
Рендеринг элементов
Базовый файл для рендеринга в приложениях React — это index.js. Прежде чем мы пойдем дальше, убедитесь, что ваш index.jsвнешний вид выглядит следующим образом:
import React from «react»;
import ReactDOM from «
import App from «. /App»;
import «. /index.css»;
ReactDOM.render (
,
document.getElementById («root»)
) ;
Кроме того, давайте проверим index.cssи убедимся, что мы сбросили значения по умолчанию для paddingи margin, выберем
@import url («https: //fonts.googleapis.com/css2? family=Montserrat&display=swap») ;
* {
margin: 0;
padding: 0;
}
body {
height: 100vh;
display: flex;
}
Наконец, давайте откроем основной файл 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 (
<Button
className=""
value="0"
onClick={ () => {
console.log («Button clicked!») ;
}}
/>
) ;
};
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 (
{
btnValues.flat ().map ((btn, i) => {
return (
<Button
key={i}
className={btn === «=" ? "equals»: ««}
value={btn}
onClick={ () => {
console.log (`${btn} clicked! `) ;
}}
/>
) ;
})
}
) ;
};
Проверьте свой терминал и убедитесь, что ваше приложение React все еще работает. Если нет, бегите, npm startчтобы запустить его снова.
Откройте браузер. Если вы следовали этому примеру, ваш текущий результат должен выглядеть так:
Дизайн приложения
Если вы хотите, вы также можете открыть инструменты разработчика браузера и проверить значения журнала для каждой нажатой кнопки.
Console.log
Определить состояния
Далее мы объявим переменные состояния, используя 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оператора, принимающего выражение и возвращающего значение после?, если выражение истинно, или после,: если выражение ложно:
Теперь давайте отредактируем Buttonкомпонент, чтобы он мог обнаруживать различные типы кнопок и выполнять назначенную функцию при нажатии определенной кнопки. Используйте код ниже:
import React, { useState } from «react»;
//...
const App = () => {
//...
return (
{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
}
/>
) ;
}) }
) ;
};
Теперь мы готовы создать все необходимые функции.
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 (
//...
) ;
};
запятаяClickHandler
Функция 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,
}) ;
}
};
инвертироватьClickHandler
Функция invertClickHandlerсначала проверяет, есть ли
// equalsClickHandler function
const invertClickHandler = () => {
setCalc ({
...calc,
num: calc.num? calc.num * -1: 0,
res: calc.res? calc.res * -1: 0,
sign: «»,
}) ;
};
процентClickHandler
Функция 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, опубликованную Emissary:
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 (
{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
}
/>
) ;
}) }
) ;
};
export default App;
Заключительные примечания
Поздравляем! Вы создали полнофункциональное и стильное приложение. Надеюсь, вы узнали
Демо
Некоторые дополнительные идеи для вас могут заключаться в добавлении некоторых научных функций или реализации памяти со списком предыдущих вычислений.
Если у вас есть