React Calculator
首先,先看结果(codepen):
我在项目中使用的技术:
- HTML、CSS、JavaScript三件套
- React
- SCSS
本项目是freeCodeCamp建议自学的学生免费学习编程 - Python、JavaScript、Java、Git 等 (freecodecamp.org)
该计算器项目需求清晰,具体可见:前端开发库项目 - 构建一个 JavaScript 计算器 | 学习 | freeCodeCamp.org
以下是我的一般需求:
- 计算器要有最基本的按钮,比如
AC
、=
、10个数字按钮0-9,-
*
/
按钮,小数点.
; - 其次,计算器应该有显示数据计算过程的地方,一个实时输入区和一个临时存储区。
- 在实时输入区域输入数字
AC
实时输入区和临时存储区的内容可以初始化,即0
- 实现加、减、乘、除操作功能,需要和
=
一起实现 - 如果在按下
=
符号继续按计算符后,应在上次计算结果的基础上进行新的计算。 -
不仅是减号,还可以作为负数符号.
实现小数功能- 最后需要注意的是,本计算器项目选择了!
CodePen
我选择在这里使用codepen,它可以很方便地调试页面,也可以很容易地引入第三方框架,并且可以免费使用!
首先,你需要创建一个账户,然后创建一个普通的账户pen
。 然后设置和引入项目所需的第三方库:
- 打开设置,将css设置为编译器
SCSS
。 - 由于要使用react,所以要将js设置为编译器
Bable
。 - 之后在
Add External Scripts/Pens
搜索并加入react
和react-dom
。
到目前为止,项目的基本设置已经完成。
HTML架构
因为用的是React,所以HTML只需要给个div挂上标签。
这里我将 id
设为
<div id="app"></div>
CSS样式
按钮的宽度和长度有比例关系,非常适合使用grid
目前,主流浏览器支持布局grid
布局。按钮是5*每个按钮的宽度为父元素的25%,高度为70%px;
.btn-box {
display: grid; grid-template-columns: repeat(4, 25%); grid-template-rows: repeat(5, 70px); }
其它款式可以自由发挥,这里就不细说了;
可参考新拟态风格代码:neumorphism.io,根据自己的喜好。
我的。贴在这里scss代码:
body { background-color: #2233dd; } #app { height: 95vh; display:flex; align-items: center; justify-content: center; } .app { user-select: none; background: #2233dd; box-shadow: 20px 20px 53px #1a27aa, -20px -20px 53px #2a3fff; width: 320px; border-radius: 5px; border: 5px solid #23d; color: white; .btn-box { display: grid; margin-top: 5px; grid-template-columns: repeat(4, 25%); grid-template-rows: repeat(5, 70px); button { cursor: pointer; border: none; outline: none; border-radius: 0px; background: #e0e0e0; padding: 5px 10px; background-color: #23d; color: white; font-size: 24px; box-sizing: border-box; &:active { box-shadow: inset 5px 5px 16px #1e2cc0,inset -5px -5px 16px #263afa; } &:hover { border: 1px solid #55f; // border-radius: 5px; background: linear-gradient(145deg, #23d, #1f2ec7); } &#clear { grid-column-start: span 2; } &#zero { grid-column-start: span 2; } &#equals { grid-row-start: span 2; }
&#subtract, &#add, &#equals {
border-left: 1px solid rgba(100,100,240, 1);
}
&#clear, &#divide {
border-bottom: 1px solid rgba(100,100,240, 1);
}
}
}
.show {
text-align: right;
}
.stag-area {
// padding: 5px 0;
font-size: 14px;
color: #aaa;
}
.input-area {
font-size: 22px;
padding: 5px 0;
border-bottom: 1px solid #66f;
}
}
具体代码也可以去我的codepen查看。
React架构
首先,创建Calculator
组件类进行计算器的渲染,包括实时输入区、暂存区以及所有的按钮。 同时,设置state
保存必要的数据:
input
保存当前输入的数字或运算符,即实时输入区的内容。stag
保存暂存区的数据operator
保存当前所使用的运算符isCalculated
保存是否已经计算isNegative
保存-
是负号还是运算符
全局变量operat
保存所有的运算符和0
(0
比较特殊,由于要做的计算器是,按下运算符时要判断是否已经计算过。如果是计算过的结果显示在实时输入区,那么这时按下0
就不是在数字后面加个0
,而是将整个实时输入区变为0
)。
这里没有把
button
当做一个子组件来写,因为封装成一个子组件
最后使用render
函数将DOM渲染进页面:ReactDOM.render(<Calculator />,document.getElementById("app"));
/* globals React, ReactDOM */
const operat = ['+','-','/','x','0'];
class Calculator extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '0',
stag: '0',
operator: '',
isCalculated: false,
isNegative: false
}
}
render() {
return (
<div className="app">
<div className="stag-area show">{
this.state.stag }</div>
<div className="input-area show" id="display">{
this.state.input }</div>
<div className="btn-box">
<button id="clear">AC</button>
<button id="divide">/</button>
<button id="multiply">x</button>
<button id="seven">7</button>
<button id="eight">8</button>
<button id="nine">9</button>
<button id="subtract">-</button>
<button id="four">4</button>
<button id="five">5</button>
<button id="six">6</button>
<button id="add">+</button>
<button id="one">1</button>
<button id="two">2</button>
<button id="three">3</button>
<button id="equals">=</button>
<button id="zero">0</button>
<button id="decimal">.</button>
</div>
</div>
)
}
}
// 渲染DOM
ReactDOM.render(<Calculator />,document.getElementById("app"));
按钮点击事件
接下来为每个按钮添加事件。
数字 1-9
首先,1-9
这9个数字的逻辑是一样的,点击直接输入对应的数字;反映在实时输入区就是在数字后面添加对应的字符。 在Calculator
类中添加函数getNumber(number) { }
:
getNumber(number) {
this.setState({
input: number,
})
}
并且为这9个数字按钮添加对应的点击事件,如:onClick={ ()=> this.getNumber('9') }
。需要注意的是,需要绑定this
的指向:this.getNumber = this.getNumber.bind(this);
加、减、乘、除按钮
因为四则运算都是二元运算符,所以它们的逻辑是相通的,即按下加/减/乘/除按钮后,将之前的数字保存到暂存区,然后等待第二个数字输入之后点击=
之后进行计算。
细节:点击按钮后,暂存区的文字是第一个数字+运算符,输入区也变成该运算符,并且将对应的this.operator
标记为该运算符,代码如下:
add() {
this.setState(state => {
return {
input: '+',
stag: state.input + '+',
operator: '+'
}
})
}
subtract() {
this.setState(state => {
return {
input: '-',
stag: state.input + '-',
operator: '-'
}
})
}
multiply() {
this.setState(state => {
return {
input: 'x',
stag: state.input + 'x',
operator: '*'
}
})
}
divide() {
this.setState(state => {
return {
input: '/',
stag: state.input + '/',
operator: '/'
}
})
}
最后,不要忘了为这4个按钮添加对应的点击事件和绑定this
的指向!
实现四则运算
加、减、乘、除按钮点击事件写好后,就需要写=
的点击事件,所有的运算都在该点击事件里。
根据this.operator
的值判断运算类型,分类进行运算,按下=
后进行运算,运算结果显示在实时输入区和暂存区,并标记为已运算isCalculated: true
:
calculator() {
const num2 = parseFloat(this.state.input);
const num1 = parseFloat(this.state.stag);
switch(this.state.operator) {
case '+': this.setState(state => {
const ans = num1 + num2;
return {
input: ans,
stag: state.stag + state.input + '=' + ans,
isCalculated: true
}
});
break;
case '-': this.setState(state => {
const ans = num1 - num2;
return {
input: ans,
stag: state.stag + state.input + '=' + ans,
isCalculated: true
}
});
break;
case '/': this.setState(state => {
const ans = num1 / num2;
return {
input: ans,
stag: state.stag + state.input + '=' + ans,
isCalculated: true
}
});
break;
case '*': this.setState(state => {
const ans = num1 * num2;
return {
input: ans,
stag: state.stag + state.input + '=' + ans,
isCalculated: true
}
});
break;
default: break;
}
}
清除按钮
AC
按钮会将一切重置为初始状态:
clearInput() {
this.setState({
input: '0',
stag: '0',
operator: '',
isCalculated: false,
isNegative: false
})
}
按钮0
getZero(){
if(this.state.isCalculated || this.state.input !== '0' ) {
// 如果是运算过的或者是0,则按下0,实时输入区应直接变为0
this.setState({
input: '0',
isCalculated: false
})
} else {
// 正常情况下直接在后面加0
this.setState(state => ({
input: state.input + '0',
isCalculated: false
}))
}
}
最后,为这个按钮添加对应的点击事件和绑定this
的指向!
立即执行逻辑
现在我们的计算器应用已经可以进行简单的四则运算了!但是,计算器需要支持,即类似于9/3+4
的运算,现在,我们的计算器并不能支持这种运算。
为了支持,就需要重写四则运算的按钮点击事件,判断一下是否要进行计算再进行后续操作,这里我的代码写的有点粗糙(能运行就行🤪):
add() {
if((this.state.stag.indexOf('+') > -1 || this.state.stag.indexOf('-') > -1 || this.state.stag.indexOf('/') > -1 || this.state.stag.indexOf('x') > -1) && this.state.stag.indexOf('=') === -1) {
this.calculator();
this.setState(state => {
return {
input: '+',
stag: state.input + '+',
operator: '+'
}
})
} else {
this.setState(state => {
return {
input: '+',
stag: state.input + '+',
operator: '+'
}
})
}
}
// ... ...
-
不仅是减号,也可以作为负数符号
需要在按下-
时进行判断,如果之前已经有按下过四则运算的按钮,那么这次按下-
就代表是将数字变为负数而不需要后续操作(return):
if(operat.includes(this.state.input)) {
this.setState(state => ({
isNegative: !state.isNegative
}))
return;
}
除了是-
这个特殊情况外,在多次按下+
/
*
按钮只需要取最后一次按下的运算符作为最终的运算符即可!
例如在除法运算函数前加入判断,其它同理:
if(operat.includes(this.state.input)) {
this.setState(state => ({
input: '/',
stag: state.stag.slice(0,-1) + '/',
operator: '/',
isNegative: false
}))
return;
}
实现小数功能
最后,考虑 .
小数功能;
首先写.
按钮点击函数,当是已经计算过了,点击.
就直接变成0.
即可;然后,当输入框内是数字且没有.
时,即可将.
直接添加到末尾。
getDecimal() {
if(this.state.isCalculated) {
this.setState({
input: '0.',
isCalculated: false
})
} else if(Number.isInteger(+this.state.input) && this.state.input.indexOf('.') === -1) {
this.setState(state => ({
input: state.input + '.'
}))
}
}
接着,需要重写=
的点击函数,用于兼容有小数的情况,加个if判断是否为整数即可:
calculator() {
let num1,num2;
if(this.state.stag.indexOf('.') > -1) {
num2 = parseFloat(this.state.input);
num1 = parseFloat(this.state.stag);
} else {
num2 = parseInt(this.state.input);
num1 = parseInt(this.state.stag);
}
switch(this.state.operator) {
case '+': this.setState(state => {
return {
input: num1 + num2,
stag: state.stag + state.input + '=' + (num1 + num2),
isCalculated: true
}
});
break;
case '-': this.setState(state => {
return {
input: num1 - num2,
stag: state.stag + state.input + '=' + (num1 - num2),
isCalculated: true
}
});
break;
case '/': this.setState(state => {
const ans = num1 / num2;
return {
input: ans,
stag: state.stag + state.input + '=' + ans,
isCalculated: true
}
});
break;
case '*': this.setState(state => {
const ans = num1 * num2;
return {
input: ans,
stag: state.stag + state.input + '=' + ans,
isCalculated: true
}
});
break;
default: break;
}
}
到这里,基本的功能已经全都实现,测试案例全部通过!🎉
最后的最后,完整代码可以在CodePen找到。