React 项目——计算器
一、总体设计
采用 React 框架实现计算器的基础功能。
1、 搭建界面如下: 1.1 计算器主要功能组件设计:calculator 组件
计算机功能实现主要有三个状态(当前操作数、上一个操作数、操作符)、五种操作(增加数字、回退数字、选择运算符、计算、清空)。
1.2 整体界面设计: 主要模块与组件Dom树如下,包含 Navbar 与 Content(Home、Calculator、Login、Register、404)组件。 Router 主要路径如下:
二、代码实现
1、各组件的建立
1.1 Navbar 组件
import React, { Component } from 'react';
import { Link} from 'react-router-dom';
class Navbar extends Component {
state = { }
render() {
return (
<nav className="navbar navbar-expand-lg navbar-light bg-light">
<div className="container">
<Link className="navbar-brand" to="/"> Web </Link>
<button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarText" aria-controls="navbarText" aria-expanded="false" aria-label="Toggle navigation">
<span className="navbar-toggler-icon"></span>
</button>
<div className="collapse navbar-collapse" id="navbarText">
<ul className="navbar-nav me-auto mb-2 mb-lg-0">
<li className="nav-item">
<Link className="nav-link" to="/home">首页</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/calculator">计算器</Link>
</li>
</ul>
<ul className="navbar-nav">
<li className="nav-item">
<Link className="nav-link" to="/login">登录</Link>
</li>
<li className="nav-item">
<Link className="nav-link" to="/register">注册</Link>
</li>
</ul>
</div>
</div>
</nav>
);
}
}
export default Navbar;
1.2 ContentBase 组件
import React, { Component } from 'react';
class ContentBase extends Component {
state = { }
render() {
return (
<div className="card" style={{marginTop: "20px", backgroundColor:"#F8F9FA"}}>
<div className="card-body">
<h4 className="card-title">{this.props.children}</h4>
<h6 className="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p className="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
<a href="/" className="card-link">Card link</a>
<a href="/" className="card-link">Another link</a>
</div>
</div>
);
}
}
export default ContentBase;
1.3 CalCulator 组件(内含多个Button组件)
import React, { Component } from 'react';
import ContentBase from './contentbase';
import { connect } from 'react-redux';
import DigitButton from './calculator/digitButton';
import ACTIONS from './../../redux/action';
import OperatorButton from './calculator/operatorButton';
class Calculator extends Component {
state = { };
render() {
return (
<ContentBase>
Standard
<div className="calculator">
<div className="output">
<div className="last-output">
{this.props.last}
{this.props.operator}
</div>
<div className="current-output">
{this.props.current}
</div>
</div>
<button className='button-ac' onClick={this.props.clear}>AC</button>
<button onClick={this.props.delete}>Del</button>
<OperatorButton operator="÷">÷</OperatorButton>
<DigitButton digit="7">7</DigitButton>
<DigitButton digit="8">8</DigitButton>
<DigitButton digit="9">9</DigitButton>
<OperatorButton operator="×">×</OperatorButton>
<DigitButton digit="4">4</DigitButton>
<DigitButton digit="5">5</DigitButton>
<DigitButton digit="6">6</DigitButton>
<OperatorButton operator="-">-</OperatorButton>
<DigitButton digit="1">1</DigitButton>
<DigitButton digit="2">2</DigitButton>
<DigitButton digit="3">3</DigitButton>
<OperatorButton operator="+">+</OperatorButton>
<DigitButton digit=".">.</DigitButton>
<DigitButton digit="0">0</DigitButton>
<button className='button-equal' onClick={this.props.evaluate}>=</button>
</div>
</ContentBase>
);
}
}
const mapStateToProps = (state,props) => {
return {
current: state.currentOperand,
last: state.lastOperand,
operator: state.operator,
}
}
const mapDispachToProps = {
delete: ()=>{
return {
type: ACTIONS.DELETE_DIGIT,
}
},
clear: ()=>{
return {
type: ACTIONS.CLEAR,
}
},
evaluate: ()=>{
return {
type: ACTIONS.EVALUTE,
}
}
}
export default connect(mapStateToProps,mapDispachToProps)(Calculator);
1.4 Button 组件 (DigitButton组件 与 OperatorButton 组件)
import React, { Component } from 'react';
import ACTIONS from '../../../redux/action';
import { connect } from 'react-redux';
class DigitButton extends Component {
state = {}
render() {
return (
<button onClick={() => { this.props.add_digit(this.props.digit) }}> {this.props.digit} </button>
);
}
}
const mapDispachToProps = {
add_digit: (digit) => {
return {
type: ACTIONS.ADD_DIGIT,
digit: digit,
}
}
};
export default connect(null, mapDispachToProps)(DigitButton);
import React, { Component } from 'react';
import { connect } from 'react-redux';
import ACTIONS from './../../../redux/action';
class OperatorButton extends Component {
state = { }
render() {
return (
<button onClick={ () => {this.props.choose_operator(this.props.operator)} }>{this.props.operator}</button>
);
}
}
const mapDispachToProps = {
choose_operator: (operator) => {
return {
type: ACTIONS.CHOOSE_OPERATOR,
operator: operator,
}
}
};
export default connect(null,mapDispachToProps)(OperatorButton);
2、Route 实现路由
App 组件: 放入子组件并实现路由。 url:Route 标签中的path 对应 Navbar 组件的 Link 标签中的 to 。
import React, { Component } from 'react';
import Navbar from './navbar';
import { Routes, Route, Navigate } from 'react-router-dom';
import Home from './content/home';
import Calculator from './content/calculator';
import Login from './content/login';
import Register from './content/register';
import NotFound from './content/notFound';
class App extends Component {
state = { }
render() {
return (
<React.Fragment>
<Navbar></Navbar>
<div className='container'>
<Routes>
<Route path='/' element={<Home/>} />
<Route path='/home' element={<Home />} />
<Route path='/calculator' element={<Calculator />} />
<Route path='/login' element={<Login />} />
<Route path='/register' element={<Register />} />
<Route path='/404' element={<NotFound />} />
<Route path='*' element={ <Navigate replace to="404" />} />
</Routes>
</div>
</React.Fragment>
);
}
}
export default App;
3、Redux 实现交互
3.1 构建 reducer 状态树(包括action.type 以及value)
操作类型(增加数字、删除数字、选择运算符、计算结果、清空)
const ACTIONS = {
ADD_DIGIT: "add_digit",
DELETE_DIGIT: "delete_digit",
CHOOSE_OPERATOR:"choose_operator",
EVALUTE: "evalute",
CLEAR: "clear"
};
export default ACTIONS;
上述将 action type 定为常量单独放在一个文件,有助于以后标识符名称的统一修改。
import ACTIONS from "./action";
const evalute = (state) => {
let {lastOperand,currentOperand,operator} = state;
let last = parseFloat(lastOperand);
let current = parseFloat(currentOperand);
let res = '';
switch(operator) {
case '+':
res = last + current;break;
case '-':
res = last - current;break;
case '×':
res = last * current;break;
case '÷':
res = last / current;break;
default:
}
return res.toString();
}
const reducer = (
state = {
currentOperand:"0",
lastOperand:"",
operator:"",
overwrite:false,
},
action
) => {
switch(action.type){
case ACTIONS.ADD_DIGIT:
if (state.overwrite === true){
if(action.digit === '.')
return{
...state,
currentOperand: '0.',
overwrite:false
}
return{
...state,
currentOperand: action.digit,
overwrite:false
}
}
if (state.currentOperand === '0' && action.digit === '0'){
return state;
}
if (state.currentOperand === '0' && action.digit !== '.'){
return {
...state,
currentOperand: action.digit,
}
}
if (state.currentOperand === '' && action.digit === '.'){
return {
...state,
currentOperand: '0.'
}
}
if (state.currentOperand.includes('.') && action.digit === '.'){
return state;
}
return {
...state,
currentOperand: state.currentOperand + action.digit,
}
case ACTIONS.DELETE_DIGIT:
if (state.overwrite === true)
return {
lastOperand: "0",
overwrite:"false"
}
if (state.currentOperand.length === 1)
return {
...state,
currentOperand: '0',
}
return {
...state,
currentOperand:state.currentOperand.slice(0,-1)
}
case ACTIONS.CLEAR:
return {
...state,
currentOperand: '0',
lastOperand:'',
operator:'',
}
case ACTIONS.CHOOSE_OPERATOR:
if(state.lastOperand === '')
return {
...state,
lastOperand: state.currentOperand,
operator: action.operator,
currentOperand: ''
}
if(state.lastOperand !== '' && state.currentOperand === '')
return {
...state,
operator: action.operator,
}
return {
...state,
lastOperand: evalute(state),
operator: action.operator,
currentOperand: '',
}
case ACTIONS.EVALUTE:
if (state.currentOperand === "" ||
state.operator === "" ||
state.lastOperand === "")
return state;
return {
...state,
currentOperand: evalute(state),
lastOperand: "",
operator:'',
overwrite: true
}
default:
return state;
}
};
export default reducer;
3.2 构建 store 树
import { configureStore } from "@reduxjs/toolkit";
import reducer from "./reducer";
const store = configureStore({
reducer: reducer,
})
export default store;
3.3 store 树相应内容(包括state、reducer)绑定对应组件
Calculator 组件:
const mapStateToProps = (state,props) => {
return {
current: state.currentOperand,
last: state.lastOperand,
operator: state.operator,
}
}
const mapDispachToProps = {
delete: ()=>{
return {
type: ACTIONS.DELETE_DIGIT,
}
},
clear: ()=>{
return {
type: ACTIONS.CLEAR,
}
},
evaluate: ()=>{
return {
type: ACTIONS.EVALUTE,
}
}
}
export default connect(mapStateToProps,mapDispachToProps)(Calculator);
DigitButton 组件:
const mapDispachToProps = {
add_digit: (digit) => {
return {
type: ACTIONS.ADD_DIGIT,
digit: digit,
}
}
};
export default connect(null, mapDispachToProps)(DigitButton);
OperatorButton 组件:
const mapDispachToProps = {
choose_operator: (operator) => {
return {
type: ACTIONS.CHOOSE_OPERATOR,
operator: operator,
}
}
};
export default connect(null,mapDispachToProps)(OperatorButton);
3.4 Button 单击事件绑定相应 mapDispachToProps 中的函数 (组件建立中的代码,按钮的单击已绑定)
三、最终呈现效果
|