React + Reduxで電卓アプリを作成

NO IMAGE

プロジェクトの作成

コマンドでプロジェクトを作成しましょう。
ディレクトリを移動して、以下を実行してプロジェクトを作成します。

create-react-app calculator

Success!」が表示されたらプロジェクトができていると思うので、
コマンド操作で作成したプロジェクトへ移動しましょう。

cd !$

パッケージを使用するためのコマンドをたたきます。
※ない場合はnpmにてインストールしてください。

yarn add redux react-redux prop-types

ファイル構成

src以下のようなファイル構成になります。

src
├── actions
    └── index.js
├── components
    └── CancelBtn.js
    └── EqualBtn.js
    └── NumBtn.js
    └── PlusBtn.js
├── containers
    └── CalculatorContainer.js
├── reducers
    ├── index.js
    └── calculator.js
├── utils
    └── actionType.js
├── index.js        
└── serviceWorker.js 

こちらのファイル構成をコマンドで作成していきます。

cd src
mkdir actions components containers reducers utils
touch actions/index.js
cd components
touch CancelBtn.js EqualBtn.js NumBtn.js PlusBtn.js
cd ../
touch containers/CalculatorContainer.js
touch reducers/calculator.js reducers/index.js
touch utils/actionTypes.js
cd ../

ファイル編集

ディレクトリsrc以下のファイルを編集していきます。

utils/actionTypes.js

export const INPUT_NUM = 'INPUT_NUM';
export const PLUS = 'PLUS';
export const CANCEL = 'CANCEL';
export const EQUAL = 'EQUAL';

reducers/index.js

import { combineReducers } from 'redux';
import calculator from './calcutor';

const reducer = combineReducers({
	calculator,
});

export default reducer;

reducers/calculator.js

import {
	INPUT_NUM,
	PLUS,
	CANCEL,
	EQUAL,
} from "../utils/actionTypes";

const initialAppState = {
	inputValue: 0,
	resultValue: 0,
	formula: '',
};

/**
 * プラスボタンが押下時の計算式確認
 * @param text 計算式
 * @param sym 記号(シンボル)
 * @returns {string|計算式}
 */
const plusFormulaCheck = (text, sym) => {
	// 末尾の文字を取得
	var tmpText = text.slice(-1);
	if ((tmpText === '+') && (sym === '=')) {
		// 末尾の「+」を削除して「=」にする
		return text.slice(0, -1) + sym;
	} else if (tmpText === '+') {
		// 末尾が「+」ならそのまま返す
		return text;
	} else {
		// 計算式に「+」をいれる
		return text + sym;
	}
}

/**
 * イコールボタンが押下時の計算式確認
 * @param text 計算式
 * @param sym 記号(シンボル)
 * @param resultValue 計算結果
 * @param answer 計算結果
 * @returns {string|計算式}
 */
const equalFormulaCheck = (text, sym, resultValue, answer) => {
	var tmpText = text.slice(-1);
	if (tmpText === '+') {
		// 末尾の「+」を削除
		return text.slice(0, -1);
	}
	// 計算結果の文字数
	var lenResult = resultValue.toString().length;
	// 最後の記号の取得
	tmpText = text.slice(text.length - (lenResult + 1), text.length - (lenResult));
	if (tmpText === '=') {
		// 最後の記号が「=」なら何もせずに返却
		return text;
	} else {
		// 「=」と計算結果を繋げて返却
		return text + sym + answer;
	}
}

/**
 * 計算式の確認
 * @param text 計算式
 * @param resultValue 計算結果
 * @returns {string|計算式}
 */
const formulaCheck = (text, resultValue) => {
	// 計算結果の文字数
	var lenResult = resultValue.toString().length;
	// 最後の記号を取得
	var tmpText = text.slice(text.length - (lenResult + 1), text.length - (lenResult));
	if (tmpText === '=') {
		// 「+」をつけて返却(今回は足し算だけの想定なので)
		return text + '+';
	} else {
		return text;
	}
}

/**
 * ボタン押下時のアクション
 * @param state
 * @param action
 * @returns state
 */
const calculator = (state = initialAppState, action) => {
	if (action.type === INPUT_NUM) {
		return {
			...state,
			inputValue: state.inputValue * 10 + action.number,
			formula: formulaCheck(state.formula, state.resultValue) + action.number,
		};
	} else if (action.type === PLUS) {
		return {
			...state,
			inputValue: 0,
			resultValue: state.resultValue + state.inputValue,
			formula: plusFormulaCheck(state.formula, '+'),
		};
	} else if (action.type === EQUAL) {
		return {
			...state,
			inputValue: 0,
			resultValue: state.resultValue + state.inputValue,
			formula: equalFormulaCheck(
				state.formula,
				'=',
				state.resultValue,
				state.resultValue + state.inputValue
			),
		};
	} else if (action.type === CANCEL) {
		return {
			...state,
			inputValue: 0,
			resultValue: 0,
			formula: '',
		};
	} else {
		return state;
	}
};

components/CancelBtn.js

import React from 'react';
import PropTypes from 'prop-types';

const CancelBtn = ({ onClick }) => (
	<button onClick={onClick}>C</button>
);

CancelBtn.propTypes = {
	onClick: PropTypes.func.isRequired,
};

export default CancelBtn;

components/EqualBtn.js

import React from 'react';
import PropTypes from 'prop-types';

const EqualBtn = ({ onClick }) => (
	<button onClick={onClick}>=</button>
);

EqualBtn.propTyes = {
	onClick: PropTypes.func.isRequired,
};

export default EqualBtn;

components/NumBtn.js

import React from 'react';
import PropTypes from 'prop-types';

const NumBtn = ({ num, onClick }) => (
	<button onClick={onClick}>{num}</button>
);

NumBtn.PropsTypes = {
	onClick: PropTypes.func.isRequired,
};

export default NumBtn;

components/PlusBtn.js

import React from 'react';
import PropTypes from 'prop-types';

const PlusBtn = ({ onClick }) => (
	<button onClick={onClick}>+</button>
);

PlusBtn.propTypes = {
	onClick: PropTypes.func.isRequired,
};

export  default PlusBtn;

actions/index.js

import {
	INPUT_NUM,
	PLUS,
	CANCEL,
	EQUAL,
} from "../utils/actionTypes";

export const onNumClick = (number) => ({
	type: INPUT_NUM,
	number,
});

export const onPlusClick = () => ({
	type: PLUS,
});

export const onCancelClick = () => ({
	type: CANCEL,
});

export const onEqualClick = () => ({
	type: EQUAL,
});

containers/CalculatorContainer.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import NumBtn from "../components/NumBtn";
import PlusBtn from "../components/PlusBtn";
import CancelBtn from "../components/CancelBtn";
import EqualBtn from "../components/EqualBtn";
import * as actoins from "../actions";

class CalculatorContainer extends Component {
	render() {
		const { calcultor, actions } = this.props;
		return(
			<div>
				<div>
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<NumBtn num={} onClick={() => actions.onNumClick()} />
				</div>
				<div>
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<NumBtn num={} onClick={() => actions.onNumClick()} />
				</div>
				<div>
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<NumBtn num={} onClick={() => actions.onNumClick()} />
				</div>
				<div>
					<NumBtn num={} onClick={() => actions.onNumClick()} />
					<PlusBtn onClick={actions.onPlusClick} />
					<EqualBtn onClick={actions.onEqualClick} />
				</div>
				<div>
					<CancelBtn onClick={actions.onCancelClick} />
				</div>
				<div>
					<div>計算式:{calculator.formula}</div>
					<div>計算結果:<span>{calculator.resultValue}</span></div>
				</div>
			</div>
		);
	}
}

const mapState = (state, ownProps) => ({
	calculator : state.calculator,
});

function mapDispatch(dispatch) {
	return {
		actions: bindActionCreators(actions, dispatch),
	};
}

export default connect(mapState, mapDispatch)(CalculatorContainer);

index.js

import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import * as serviceWorker from './serviceWorker';
import CalculatorContainer from './containers/CalculatorContainer';
import reducer from './reducers';

const store = createStore(reducer);

render(
  <Provider store={store}>
    <CalculatorContainer />
  </Provider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

アプリを実行

コマンドでアプリを実行しましょう。

yarn start

ボタンを押して、計算してみましょう。
無事に計算式と計算結果がでたら成功です。

※計算式の簡単な制御は入れてありますが、複雑な操作をするとおかしくなります。

以上、React + Reduxでの電卓アプリ作成でした。

JavaScriptカテゴリの最新記事