Учебное пособие по React: создание приложения-калькулятора с нуля

React Изучение

В этом руководстве мы создадим приложение React Calculator. Вы узнаете, как создавать каркас, разрабатывать макет, создавать компоненты, обновлять состояния и форматировать выходные данные.

Планирование

Поскольку мы будем создавать приложение «Калькулятор», давайте выберем область, которая не слишком сложна для изучения, но также не слишком проста для покрытия различных аспектов создания приложения.

Мы реализуем следующие функции:

  • сложить, вычесть, умножить, разделить
  • поддержка десятичных значений
  • рассчитать проценты
  • инвертировать значения
  • сбросить функциональность
  • форматировать большие числа
  • изменение размера вывода в зависимости от длины

Для начала мы нарисуем базовый каркас для отображения наших идей. Для этого вы можете использовать бесплатные инструменты, такие как Figma или Diagrams.net .

Для начала мы нарисуем базовый каркас для отображения наших идей

Обратите внимание, что на этом этапе не так важно думать о цветах и ​​стиле. Важнее всего то, что вы можете структурировать макет и идентифицировать задействованные компоненты.

Цвета дизайна

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

Ниже приведены некоторые рекомендации по улучшению внешнего вида приложения:

  • обертка должна контрастировать с фоном
  • значения экрана и кнопок должны быть легко читаемыми
  • кнопка равенства должна быть другого цвета, чтобы сделать акцент

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

Основываясь на приведенных выше критериях, мы будем использовать

Настройка проекта

Для начала откройте терминал в папке проектов и создайте шаблонный шаблон с помощью приложения create-response-app . Для этого выполните команду:

npx create-react-app calculator

Это самый быстрый и простой способ настроить полностью работающее приложение React с нулевой конфигурацией. Все, что вам нужно сделать после этого, — это запустить, cd calculatorчтобы переключиться во вновь созданную папку проекта и npm startзапустить приложение в браузере.

Как видите, он поставляется с некоторым шаблоном по умолчанию

Как видите, он поставляется с некоторым шаблоном по умолчанию, поэтому теперь мы немного очистим дерево папок проекта.

Найдите srcпапку, в которой будет жить логика вашего приложения, и удалите все, кроме App.jsсоздания вашего приложения, index.cssстилизации вашего приложения и index.jsвизуализации вашего приложения в DOM.

Найдите srcпапку, в которой будет жить логика вашего приложения

Создать компоненты

Поскольку мы уже сделали каркасную модель, мы уже знаем основные строительные блоки приложения. Те 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;

Заключительные примечания

Поздравляю! Вы создали полностью функциональное и стильное приложение. Надеюсь, вы узнали кое-что в процессе!

Некоторые дальнейшие идеи, которые вы можете изучить

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

Читайте также:  Лучшие PHP-фреймворки для веб-разработки
Оцените статью
bestprogrammer.ru
Добавить комментарий