در این آموزش ، ما یک برنامه React Calculator می سازیم. شما خواهید آموخت که چگونه یک وایرفریم بسازید ، یک طرح بندی طراحی کنید ، اجزاء را ایجاد کنید ، حالت ها را به روز کنید و قالب خروجی را قالب بندی کنید.

برای الهام گرفتن از شما ، اینجاست پیوندی به پروژه مستقر شده خواهیم ساخت

همچنین ، در اینجا این است کد منبع، در صورت نیاز به کمک در هر مرحله از پروژه ، فقط برای مرجع است.

برنامه ریزی

از آنجا که ما در حال ساخت یک برنامه ماشین حساب هستیم ، بیایید محدوده ای را انتخاب کنیم که برای یادگیری بسیار پیچیده نیست اما برای پوشش جنبه های مختلف ایجاد یک برنامه نیز چندان اساسی نیست.

ویژگی هایی که پیاده سازی می کنیم عبارتند از:

  • جمع ، تفریق ، ضرب ، تقسیم
  • پشتیبانی از مقادیر اعشاری
  • درصد محاسبه کنید
  • مقادیر معکوس
  • تنظیم مجدد عملکرد
  • اعداد بزرگتر را قالب بندی کنید
  • تغییر اندازه خروجی بر اساس طول

برای شروع ، ما یک قاب اصلی را برای نمایش ایده های خود ترسیم می کنیم. برای این کار می توانید از ابزارهای رایگان مانند فیگما یا Diagrams.netبه

قاب سیم

توجه داشته باشید که در این مرحله ، فکر کردن در مورد رنگ و استایل چندان مهم نیست. مهمتر از همه این است که می توانید طرح بندی را ساختار بندی کرده و اجزای مربوطه را شناسایی کنید.

طراحی رنگ ها

هنگامی که به طرح و اجزای آن پرداختیم ، تنها کاری که برای تکمیل طراحی باقی می ماند انتخاب یک رنگ بندی زیبا است.

در زیر برخی از دستورالعمل ها برای جلوه دادن برنامه عالی است:

  • بسته بندی باید با پس زمینه تضاد داشته باشد
  • ارزش صفحه و دکمه باید به راحتی خوانده شود
  • دکمه equals باید رنگ متفاوتی داشته باشد تا کمی تأکید شود

بر اساس معیارهای بالا ، ما از رنگ بندی نشان داده شده در زیر استفاده می کنیم.

طرح رنگی

راه اندازی پروژه

برای شروع ، ترمینال موجود در پوشه پروژه های خود را باز کرده و یک قالب boilerplate با استفاده از ایجاد-واکنش-برنامهبه برای انجام این کار ، دستور را اجرا کنید:

npx create-react-app calculator

این سریع ترین و ساده ترین راه برای راه اندازی یک برنامه کاملاً فعال React با پیکربندی صفر است. تنها کاری که باید انجام دهید پس از اجرا است cd calculator برای تغییر به پوشه پروژه ایجاد شده جدید و npm start برای شروع برنامه خود در مرورگر

نمای مرورگر

همانطور که می بینید ، دارای برخی از صفحات بویلر پیش فرض است ، بنابراین در مرحله بعد ما در درخت پوشه پروژه تمیزکاری می کنیم.

پیدا کن src پوشه ای که منطق برنامه شما در آن زنده است و همه چیز را حذف کنید به جز App.js برای ایجاد برنامه خود ، index.css به سبک برنامه خود ، و index.js برای ارائه برنامه خود در DOM

درخت پروژه

ایجاد کامپوننت ها

از آنجا که ما قبلاً برخی از فریم های سیم کشی را انجام داده ایم ، در حال حاضر اجزای اصلی برنامه را می شناسیم. آنها هستند Wrapper، Screen، ButtonBox، و Buttonبه

ابتدا a ایجاد کنید 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 <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 جزء فرزند برتر بخش خواهد بود Wrapper ، و هدف آن نمایش مقادیر محاسبه شده خواهد بود.

در لیست ویژگی ها ، ما اندازه خروجی صفحه نمایش را در طول قرار دادیم ، به این معنی که مقادیر طولانی تر باید کوچک شوند. ما از یک کتابخانه کوچک (3.4kb gzip) به نام استفاده می کنیم react-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 جزء تعاملی را برای برنامه ارائه می دهد. هر جزء دارای value و onClick لوازم

در شیوه نامه ، ما همچنین سبک های مربوط به equal دکمه. استفاده خواهیم کرد Button برای دسترسی به کلاس بعداً آماده می شود.

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، چند فونت عالی انتخاب کنید (مانند مونتسرات در این مورد) و قوانین مناسب را برای قرار دادن برنامه در نمای نمایش تنظیم کنید:

@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 جزء.

بیایید یک نمایش آرایه از داده ها در wireframe ایجاد کنیم ، بنابراین می توانیم تمام دکمه های موجود در نقشه را ترسیم کرده و رندر کنیم ButtonBox:

import Wrapper from "./components/Wrapper";
import Screen from "./components/Screen";
import ButtonBox from "./components/ButtonBox";
import Button from "./components/Button";

const btnValues = [
  ["C", "+-", "%", "https://www.sitepoint.com/"],
  [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 تا دوباره شروع کنم

مرورگر خود را باز کنید. در صورت دنبال کردن ، نتیجه فعلی شما باید به این شکل باشد:

طراحی برنامه

در صورت تمایل ، همچنین می توانید devtools مرورگر را باز کرده و مقادیر log را برای هر دکمه فشرده آزمایش کنید.

Console.log

حالت ها را تعریف کنید

سپس ، متغیرهای حالت را با استفاده از React اعلام می کنیم useState قلاب.

به طور خاص ، سه حالت وجود خواهد داشت: 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 جزء. منطق شرطی زیر را روی value prop ، بنابراین عدد وارد شده (در صورت تایپ شماره) یا نتیجه محاسبه شده (در صورت فشردن دکمه برابر) را نمایش می دهد.

برای این کار ، از 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 === "https://www.sitepoint.com/" || btn === "X" || btn === "-" || btn === "+"
                  ? signClickHandler
                  : btn === "."
                  ? comaClickHandler
                  : numClickHandler
              }
            />
          );
        })}
      </ButtonBox>
    </Wrapper>
  );
};

اکنون آماده ایم تا همه توابع لازم را ایجاد کنیم.

numClickHandler

این numClickHandler عملکرد فقط در صورت فشردن هر یک از دکمه های شماره (0 تا 9) فعال می شود. سپس مقدار the را دریافت می کند 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 (
    
  );
};

comaClickHandler

این comaClickHandler عملکرد فقط در صورت فشار دادن مقدار کما (‘.’) فعال می شود. کما را به جریان اضافه می کند num مقدار ، آن را به یک عدد اعشاری تبدیل می کند.

همچنین اطمینان حاصل می کند که امکان ویرگول چندگانه وجود ندارد.



const comaClickHandler = (e) => {
  e.preventDefault();
  const value = e.target.innerHTML;

  setCalc({
    ...calc,
    num: !calc.num.toString().includes(".") ? calc.num + value : calc.num,
  });
};

signClickHandler

این signClickHandler هنگامی که کاربر یا یکی را فشار می دهد عملکرد روشن می شود +، ، * یا /به سپس مقدار خاص به عنوان جریان تنظیم می شود sign ارزش در calc هدف – شی.

همچنین اطمینان حاصل می کند که هیچ تأثیری بر تماس های مکرر ندارد:



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 تقسیم کنند


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 === "https://www.sitepoint.com/"
          ? "Can't divide with 0"
          : math(Number(calc.res), Number(calc.num), calc.sign),
      sign: "",
      num: 0,
    });
  }
};

invertClickHandler

این invertClickHandler تابع ابتدا بررسی می کند که آیا مقدار وارد شده وجود دارد (num) یا مقدار محاسبه شده (res) و سپس آنها را با ضرب در -1 معکوس می کند:



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 تابع ، که پایه را به توان توان باز می گرداند:



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 زمانی که برنامه ماشین حساب برای اولین بار ارائه شد ، وضعیت آن را نشان دهید:



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 به توابع handler برای 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", "+-", "%", "https://www.sitepoint.com/"],
  [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 comaClickHandler = (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 === "https://www.sitepoint.com/"
            ? "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 === "https://www.sitepoint.com/" || btn === "X" || btn === "-" || btn === "+"
                  ? signClickHandler
                  : btn === "."
                  ? comaClickHandler
                  : numClickHandler
              }
            />
          );
        })}
      </ButtonBox>
    </Wrapper>
  );
};

export default App;

نکات پایانی

تبریک می گویم! شما یک برنامه کاملاً کاربردی و سبک ایجاد کرده اید. امیدوارم در طی این فرآیند یک یا دو چیز یاد گرفته باشید!

نسخه ی نمایشی

برخی از ایده های دیگر برای شما این است که برخی ویژگی های علمی را اضافه کنید یا حافظه را با لیست محاسبات قبلی پیاده سازی کنید.

اگر هرگونه گزارش مشكل یا درخواست ویژگی دارید ، با خیال راحت آنها را در این قسمت بگذارید بازپرداخت GitHubبه اگر از پروژه خوشتان می آید ، با خیال راحت آن را ستاره کنید.