用React做一个新拟态计算器

React Calculator

首先,先看成果(codepen):

react-calculator.png

项目中我使用到的技术:

  • HTML、CSS、JavaScript三件套
  • React
  • SCSS

本项目是freeCodeCamp上的其中一个前端开发库项目,推荐自学的同学去免费学习编程 - Python、JavaScript、Java、Git 等 (freecodecamp.org)

这个计算器项目需求很清晰,具体可看:前端开发库项目 - 构建一个 JavaScript 计算器 | 学习 | freeCodeCamp.org

这里列出我的大致需求:

  1. 计算器要有最基本的按钮,比如AC=、0-9这10个数字按钮、+ - * / 按钮、小数点.
  2. 其次,计算器要有展示数据计算过程的地方,一个实时输入区,一个暂存区。
  3. 按下数字可以在实时输入区输入数字
  4. AC可以初始化实时输入区和暂存区的内容,即0
  5. 加、减、乘、除操作的功能实现,需要和=一起实现
  6. 如果在按下 = 符号后继续按一个运算符,则应该在上一次计算结果的基础上进行新的计算。
  7. - 不仅是减号,也可以作为负数符号
  8. . 实现小数功能
  9. 最后需要注意的是,本计算器项目选用的是立即执行逻辑

CodePen

我这里选择使用codepen,它可以非常方便的调试页面,还能够很方便的引入第三方框架,而且可以免费使用!

首先,需要创建账户,然后新建一个普通的pen
接着需要进行设置和引入项目需要的第三方库:

  • 打开设置,将css编译器设置为SCSS
  • 由于要使用react,所以要将js编译器设置为Bable
  • 之后在 Add External Scripts/Pens里搜索并加入 reactreact-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保存所有的运算符和00比较特殊,由于要做的计算器是立即执行逻辑,按下运算符时要判断是否已经计算过。如果是计算过的结果显示在实时输入区,那么这时按下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;
  }
}

到这里,基本的功能已经全都实现,测试案例全部通过!🎉
image.png

最后的最后,完整代码可以在CodePen找到。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值