在本教程中,我们将构建一个 React Calculator 应用程序。您将学习如何制作线框、设计布局、创建组件、更新状态和格式化输出。
在此之前我们先将下载链接贡献出来,如下:
https://url18.ctfile.com/f/7715018-576965728-984224?p=6511 (访问密码:6511)
规划
由于我们将构建一个计算器应用程序,因此让我们选择一个对于学习来说不太复杂但对于涵盖创建应用程序的不同方面也不太基本的范围。
我们将实现的功能包括:
- 加、减、乘、除
- 支持十进制值
- 计算百分比
- 反转值
- 重置功能
- 格式化更大的数字
- 根据长度调整输出大小
首先,我们将绘制一个基本的线框来展示我们的想法。为此,您可以使用Figma或Diagrams.net等免费工具。

请注意,在这个阶段,考虑颜色和样式并不重要。最重要的是您可以构建布局并识别所涉及的组件。
设计颜色
一旦我们处理了布局和组件,剩下要做的就是选择一个漂亮的配色方案。
以下是一些使应用程序看起来很棒的指南:
- 包装应该与背景形成对比
- 屏幕和按钮值应该易于阅读
- 等号按钮应该采用不同的颜色,以提供一些重音
根据上述标准,我们将使用如下所示的配色方案。

设置项目
首先,在您的项目文件夹中打开终端并使用create-React-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.js
import "./Wrapper.css";
const Wrapper = ({ children }) => {
return <div className="wrapper">{children}</div>;
};
export default 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组件,其目的是显示计算值。
在功能列表中,我们包含了显示输出长度调整大小,这意味着较长的值必须缩小大小。我们将为此使用一个名为react-textfit的小型 (3.4kb gzip) 库。
要安装它,运行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;
屏幕.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组件类似,Wrapper组件将是子组件的框架——只是这次是Button组件。
按钮框.js
import "./ButtonBox.css";
const ButtonBox = ({ children }) => {
return <div className="buttonBox">{children}</div>;
};
export default 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按钮的样式。稍后我们将使用Buttonprops 来访问该类。
按钮.js
import "./Button.css";
const Button = ({ className, value, onClick }) => {
return (
<button className={className} onClick={onClick}>
{value}
</button>
);
};
export default 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并确保我们重置了 and 的默认值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以重新启动它。
打开浏览器。如果你跟着,你当前的结果应该是这样的:

如果需要,您还可以打开浏览器的开发工具并测试每个按下按钮的日志值。

定义状态
接下来,我们将使用 ReactuseState钩子声明状态变量。
具体来说,会有三种状态: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三元运算符,它基本上是语句的快捷方式,接受一个表达式并在表达式为 true 或表达式为 falseif之后返回一个值:?:
<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小数点 ( ) 时才会触发该函数。.它将小数点添加到当前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函数会被触发。然后将特定值设置为对象中的当前值。signcalc
它还将确保对重复调用没有影响:
// 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 )时计算结果。计算基于电流和值,以及选择(见函数)。numressignmath
然后将返回的值设置为新值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函数首先检查是否有任何输入值 ( 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函数检查是否有任何输入值 ( 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函数默认 的所有初始值calc,返回calcCalculator 应用程序首次呈现时的状态:
// percentClickHandler function
const resetClickHandler = () => {
setCalc({
...calc,
sign: "",
num: 0,
res: 0,
});
};
输入格式
完成介绍中的功能列表的最后一件事是实现值格式化。为此,我们可以使用Emissary发布的修改过的 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;
最后的笔记
恭喜!您已经创建了一个功能齐全且样式齐全的应用程序。希望您在此过程中学到了一两件事!

您需要探索的一些进一步的想法是添加一些科学特征,或者使用以前的计算列表来实现内存。

如若转载,请注明出处:https://www.zzibang.com/4014.html