React Calculator
首先,先看成果(codepen):
项目中我使用到的技术:
- HTML、CSS、JavaScript三件套
- React
- SCSS
本项目是freeCodeCamp上的其中一个前端开发库项目,推荐自学的同学去免费学习编程 - Python、JavaScript、Java、Git 等 (freecodecamp.org)
这个计算器项目需求很清晰,具体可看:前端开发库项目 - 构建一个 JavaScript 计算器 | 学习 | freeCodeCamp.org
这里列出我的大致需求:
- 计算器要有最基本的按钮,比如
AC 、= 、0-9这10个数字按钮、+ - * / 按钮、小数点. ; - 其次,计算器要有展示数据计算过程的地方,一个实时输入区,一个暂存区。
- 按下数字可以在实时输入区输入数字
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*4的布局,每个按钮宽度为父元素的25%,高度设置为70px;
.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"));
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>
)
}
}
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' ) {
this.setState({
input: '0',
isCalculated: false
})
} else {
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找到。
|