[一步一步學react系列] 04—計算器Demo
阿新 • • 發佈:2019-02-17
前言:
之前的例子都是寫的計數器,加一減一的功能,我們大致弄懂了redux分層和store資料管理,下面我們將結合現有知識寫一個終極版的計算器。以此鞏固所學知識
知識點:redux分層,react-router,一些演算法及資料結構知識[棧 + 中綴轉字尾]
效果圖
專案結構
├──redux-demo/ * 計算器Demo
|
|————src/ * 主程式
│
├─Components * 所有元件
│ ├─Calculator * 計算器
│ ├─Counter * 計數器
│ └─StudyDemos * 學習的一些有幫助的demo
│ └─備份資料夾 * 筆記 等我寫完部落格就清
├─Error * 錯誤元件
├─Redux * Redux
│ ├─Action
│ ├─Containers
│ ├─Reducer
│ └─Store
├─Router * 路由
└─Style * 所有樣式變數
為了便於初學者如我上手,我沒有將分層的各個功能部件寫在不同的檔案下,而是採用了先寫在一起然後分成多個檔案的策略。如下是我的計算器程式碼,並沒有加路由。
思路是:
1.佈局:按鈕值是陣列,迴圈陣列值生成按鈕,一個函式監聽所有的點選事件。flex佈局,固定每行幾個塊。
2.確定當前例項中擁有的所有資料,接下來在這些資料中找出應該是state的資料。
即:在當前例項[計算器]中的所有資料,確定哪些是本元件內部管理的無需存到store上的資料。
也就是確定props資料與state資料。
props資料與state資料劃分遵循三原則:
1. 是否是通過父級props傳來的,如果是則可能不是state
2. 會隨時間推移而不變嗎? 如果是則可能不是state
3. 你能根據元件中其他任何的state或者props計算出他嗎?如果能,則可能不是state
由上推理:
- 等號及等號前的資料: 通過使用者的輸入而來 會隨時間推移而變 state
- 等號後的資料: 能夠計算得出
- 按鈕的值: 來源於父級props層層傳遞而來
綜上我們可以得到屬於本元件state狀態的資料是:等號及等號前的資料
而每次計算的結果我們則存到redux的store裡。即結果來源於props。
關於結果:
我們對應一個方法和一個引數
- 方法:等於符號的點選 equalClick => 負責向外分發action
- 引數: revdata 結果值 =>繼承自strore,this.props.revdata
原始碼解讀:
import React, {Component} from 'react' ;
import { createStore } from 'redux'
import { connect } from 'react-redux'
import suffixExpression from './stack'
import '../../Style/calcuator.css'
const KEYVALUE = [
{value: '7'},
{value: '8'},
{value: '9'},
{value: '←'},
{value: 'C'},
{value: '4'},
{value: '5'},
{value: '6'},
{value: '*'},
{value: '/'},
{value: '1'},
{value: '2'},
{value: '3'},
{value: '+'},
{value: '-'},
{value: '0'},
{value: '00'},
{value: '.'},
{value: '%'},
{value: '='},
{value: '('},
{value: ')'}
];
class MyCalculator extends Component {
constructor(props){
super(props);
this.state = {
valueText: '0' //實時更新使用者輸入的值
}
}
handleValueInput(data) {
let oldState = this.state.valueText;
//傳入當前文字框的值和當前按鈕按下的值,呼叫checkClickType依據不同的按鈕值做不同的反應,返回新的值。
let rev = this.checkClickType(oldState,data);
let newState = {};
newState.valueText = rev;
this.setState( newState)
}
checkClickType(oldvalue,value){
switch (value) {
case '=':
let resultbefore = oldvalue + ' =' ;
//向外分發action
this.props.equalClick(oldvalue);
return resultbefore;
case '←':
//刪除最後一位
oldvalue = oldvalue.substring(0,oldvalue.length-1)
return oldvalue;
case 'C':
oldvalue = '0';
return oldvalue;
case '+':
case '-':
case '/':
case '*':
case '(':
case ')':
return oldvalue + ' ' +value + ' ';//運算子與運算元以空格為分割
default://一般數字
if(oldvalue === '0'){//清零
oldvalue = ''
}
return oldvalue + value
}
}
render() {
const {revdata} = this.props;//獲得最新的結果值
let buttonlist = [];
KEYVALUE.forEach(data => {
buttonlist.push(
<button className='div_class_button'
key={data.value}
onClick = {this.handleValueInput.bind(this,data.value)}
>{data.value}</button>
);
});
//取當前input框字串的最後一個字元 如果是等於符號則 運算過程+結果
let str = this.state.valueText;
let laststr = str.charAt(str.length - 1)
let curValue = str;
if(laststr === '='){
curValue = str +' '+revdata;
}
return (
<div className='div_class_calculator'>
<div className='div_class_showdatabar'>
<h1>簡易計算器</h1>
<input type="text"
value={curValue}
readOnly
/>
</div>
<div className='div_class_buttonlist'>
{buttonlist}
</div>
</div>
);
}
}
/**
* @func 模組--container
* @desc 定義對映
*/
//將UI元件的props與redux的state對映
function mapStateToProps(state) {
return {
revdata: state.revdata
}
}
//將UI元件的props與redux的action對映
function mapDispatchToProps(dispatch) {
return {
//使用者的onIncreaseClick方法與action對映([3]定義action),通過dispatch觸發reducer
equalClick: (value) => dispatch(getResult(value))
}
}
/**
* @func 模組--action
* @desc
*/
const EQUEALBTN = 'EQUEALBTN'; //常規按鈕
const ActionGenerator = (type, num) => (num) => {
let action = { type, num : num }
return action
}
const getResult = ActionGenerator(EQUEALBTN, null);
/**
* @func 模組--connect
*/
const App = connect(
mapStateToProps,
mapDispatchToProps
)(MyCalculator)
/**
* @func 模組--reducer
* @desc 根據action 返回新的state
*/
function getRev(state = { revdata: 0 }, action) {
//action.num即是等號前面的字串
switch (action.type) {
case EQUEALBTN:
//let test = '1 + 78 + 22 + ( 10 - 2 ) * 6';
let rev = suffixExpression(action.num)//具體的計算處理,我採用的是中綴轉字尾計算方法。
return { revdata: rev }
default:
return state
}
}
/**
* @func 模組--store
* @desc 以reducer生成store物件
*/
const store = createStore(getRev)
export {
store,
App
};
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import {store,App} from './Components/Calculator/calculatorAll';
ReactDOM.render(
<Provider store={store}>
{route}
</Provider>,
document.getElementById('root')
);