『React』+『Redux』でiPhoneに入ってるような電卓を作ってみよう！

#はじめに
前回の記事で『Todo』の実装を終えて、次に何を作ろうか悩んでいた時にこのような記事を見つけました。

- [電卓アプリで学ぶReact/Redux入門(実装編)](https://qiita.com/nishina555/items/9ff744a897af8ed1679b)

今回はこの記事を参考にし、電卓アプリケーションを作ることによりReactとReduxの理解を深めていきたいと思います！

前回の記事

- [React + ReduxでTodoアプリを作ってみよう！『Filter Todo編』](https://qiita.com/micropig3402/items/bb5ca76c3b1c5eac2526)

#完成品
![ezgif.com-optimize (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/3de1e40b-b21a-a5c7-7458-e867c4c2d31e.gif)

#準備
##何はともあれcreate-react-appから
terminalで以下のコマンドを入力しましょう！

```
$ create-react-app calculator
$ cd calculator
$ yarn start
```
これで、以下のようにReactを用いたWebアプリケーションを作成する準備ができました！

<img width="665" alt="スクリーンショット 2019-05-15 10.01.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/1dcde7e2-da4c-67b4-e8e8-e59de94c9c59.png">

`create-react-app`がわからない人はこちらの記事へ

- [ReactでHelloWorldをしてみよう！](https://qiita.com/micropig3402/items/b90afc78f439f9bec81a)

##Reduxのインストール
さらに今回はReduxを用いるので、以下のコマンドを入力してReduxのパッケージをインストールしましょう！

```
yarn add react-redux redux
```
これで準備完了です！

#早速実装してみよう！

##見た目から入る
まずは電卓らしい見た目を作っていきましょう！以下の３つのComponentを作成します

『0~9の数字ボタン(NumBtn.js)』

```react:src/components/NumBtn.js
import React from 'react';

const NumBtn = ({n}) => <button>{n}</button>;

export default NumBtn;
```

『+, -, ×, ÷の計算ボタン(OperatorBtn.js)』

```react:src/components/OperatorBtn.js
import React from 'react';

const OperatorBtn = ({o}) => <button>{o}</button>;

export default OperatorBtn;
```
『計算結果を表示する(Result.js)』

```react:src/components/Result.js
import React from 'react';

const Result = ({result}) => <div className="resultValue">{result}</div>;

export default Result;
```

この3つのComponentを`App.js`にimportしましょう！

```react:src/App.js
import React from 'react';

import './App.css';
import NumBtn from './components/NumBtn';
import OperatorBtn from './components/OperatorBtn';
import Result from './components/Result';

function App() {
  return (
    <React.Fragment>
      <div className="result">
        <Result result={'計算結果'} />
      </div>
      <div className="wrapper">
        <div className="number">
          <div className="numUpper">
            <NumBtn n={7} />
            <NumBtn n={8} />
            <NumBtn n={9} />
          </div>
          <div className="numMiddle">
            <NumBtn n={4} />
            <NumBtn n={5} />
            <NumBtn n={6} />
          </div>
          <div className="numLower">
            <NumBtn n={1} />
            <NumBtn n={2} />
            <NumBtn n={3} />
          </div>
          <div className="zero">
            <NumBtn n={0} />
            <span className="allClear">
              <OperatorBtn o={'AC'} />
            </span>
            <span className="equal">
              <OperatorBtn o={'='} />
            </span>
          </div>
        </div>
        <div className="operator">
          <OperatorBtn o={'÷'} />
          <OperatorBtn o={'×'} />
          <OperatorBtn o={'-'} />
          <OperatorBtn o={'+'} />
        </div>
      </div>
    </React.Fragment>
  );
}

export default App;
```

そして`App.css`にてスタイルを変更すると

```css:src/App.css
  #root {
    width:200px;
    margin: 40px auto;
  }
  
  .result {
    height: 40px;
    background: black;
  }
  
  .resultValue {
    text-align: end;
    height: 100%;
    font-size: 30px;
    color: white;
    padding-right: 15px; 
  }
  
  .wrapper {
    display: flex;
    justify-content: center;
    background: black;
  }
  
  button {
    border-radius: 50%;
    width: 40px;
    height: 40px;
    margin: 5px;
    background: #555555;
    border: none;
    color: white;
    font-size: 25px;
    padding: 0;
  }
  
  button:hover {
    background: #999999;
  }
  
  button:focus {
    outline: none;
  }
  
  .operator button {
    background: orange;
  }
  
  .operator button:hover{
    background: #FFC7AF;
  }
  
  .operator button:focus {
    background: white;
    color: orange;
  }
  
  .operator {
    display: flex;
    flex-direction: column;
  }
```

出力画面がこのようになります。
<img width="265" alt="スクリーンショット 2019-05-15 11.01.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/609f9a72-1724-05e4-385e-8de76f17383c.png">

`App.css`を変更して、ご自身の好きなデザインにするのも面白いかもしれませんね！

これにて見た目は完成です！

#中身を実装しよう
##Action
Componentを押された時に発行されるActionの定義を行なっていきましょう！ここでは

- 『数字』が押された時
- 『+、-、×、÷』が押された時
- 『=』が押された時
- 『AC』が押された時

のActionの定義を行なっていきたいと思います。

まずは、Actionのtypeの定義から

```react:src/utils/actionTypes.js
export const INPUT_NUMBER = 'INPUT_NUMBER';

export const PLUS = 'PLUS';
export const MINUS = 'MINUS';
export const MULTIPLY = 'MULTIPLY';
export const DIVIDE = 'DIVIDE';

export const EQUAL = 'EQUAL';
export const CLEAR = 'CLEAR';
```

次にActionクリエイターを作成します。

```react:src/actions/index
import * as actionTypes from '../utils/actionTypes';

export const onNumClick = number => ({
  type: actionTypes.INPUT_NUMBER,
  number,
});

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

export const onMinusClick = () => ({
  type: actionTypes.MINUS,
});

export const onMultiplyClick = () => ({
  type: actionTypes.MULTIPLY,
});

export const onDivideClick = () => ({
  type: actionTypes.DIVIDE,
});

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

export const onClearClick = () => ({
  type: actionTypes.CLEAR,
});
```

##Reduer
発行されたActionにより、どのように状態を遷移させるのかを記述するのがReducerです。電卓を作るのに必要な以下の5つの状態を遷移させます。

- inputValue(入力された値)
- operator(演算子)
- resultValue(計算結果)
- calculate(計算を行うかの判断)
- showingResult(結果を表示するかの判断)

`case actionTypes.EQUAL:`では直前に押された演算子を`operator`に保存しておくことで、どのような演算を行うのかを判断しています。

`calculate`は演算子ボタンを押した時に演算を行うかどうか判断するためのものです。これがないと、GIFのように最初の状態から『8-5』を計算したいときに、『8-』を押した際に『0-8』が計算され、その後『-8-5』が計算されるので、結果が『-13』となってしまいます。

![ezgif.com-optimize (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/343290/0b88af89-4e34-2861-e614-5b0769fecfff.gif)


```react:src/reducers/calculator.js
import * as actionTypes from '../utils/actionTypes';

const initialAppState = {
  inputValue: 0,
  operator: '',
  resultValue: 0,
  calculate: false,
  showingResult: false,
};

const calculator = (state = initialAppState, action) => {
  switch (action.type) {
    case actionTypes.INPUT_NUMBER:
      return {
        ...state,
        inputValue: state.inputValue * 10 + action.number,
        showingResult: false,
      };

    case actionTypes.PLUS:
      if (state.calculate === true) {
        return {
          ...state,
          inputValue: 0,
          operator: '+',
          resultValue: state.resultValue + state.inputValue,
          showingResult: true,
        };
      } else {
        return {
          ...state,
          inputValue: 0,
          operator: '+',
          calculate: true,
          resultValue: state.inputValue,
          showingResult: true,
        };
      }

    case actionTypes.MINUS:
      if (state.calculate === true) {
        return {
          ...state,
          inputValue: 0,
          operator: '-',
          resultValue: state.resultValue - state.inputValue,
          showingResult: true,
        };
      } else {
        return {
          ...state,
          inputValue: 0,
          operator: '-',
          calculate: true,
          resultValue: state.inputValue,
          showingResult: true,
        };
      }

    case actionTypes.MULTIPLY:
      if (state.calculate === true) {
        return {
          ...state,
          inputValue: 0,
          operator: '*',
          resultValue: state.resultValue * state.inputValue,
          showingResult: true,
        };
      } else {
        return {
          ...state,
          inputValue: 0,
          operator: '*',
          calculate: true,
          resultValue: state.inputValue,
          showingResult: true,
        };
      }

    case actionTypes.DIVIDE:
      if (state.calculate === true) {
        return {
          ...state,
          inputValue: 0,
          operator: '/',
          resultValue: state.resultValue / state.inputValue,
          showingResult: true,
        };
      } else {
        return {
          ...state,
          inputValue: 0,
          operator: '/',
          calculate: true,
          resultValue: state.inputValue,
          showingResult: true,
        };
      }

    case actionTypes.CLEAR:
      return {
        inputValue: 0,
        operator: '',
        calculate: false,
        resultValue: 0,
        showingResult: false,
      };
    case actionTypes.EQUAL:
      switch (state.operator) {
        case '+':
          return {
            inputValue: state.resultValue + state.inputValue,
            operator: '',
            calculate: false,
            resultValue: state.resultValue + state.inputValue,
            showingResult: true,
          };
        case '-':
          return {
            inputValue: state.resultValue - state.inputValue,
            operator: '',
            calculate: false,
            resultValue: state.resultValue - state.inputValue,
            showingResult: true,
          };
        case '*':
          return {
            inputValue: state.resultValue * state.inputValue,
            operator: '',
            calculate: false,
            resultValue: state.resultValue * state.inputValue,
            showingResult: true,
          };
        case '/':
          return {
            inputValue: state.resultValue / state.inputValue,
            operator: '',
            calculate: false,
            resultValue: state.resultValue / state.inputValue,
            showingResult: true,
          };
        default:
          return state;
      }

    default:
      return state;
  }
};

export default calculator;
```

今回は１つのReducerしか作っていませんが、以下のようにconbineReducersを用いてReducerを結合させます。

```react:src/reducers/index.js
import {combineReducers} from 'redux';
import calculator from './calculator';

const reducer = combineReducers({
  calculator,
});

export default reducer;
```

##Store
次は先ほど作成したReducerをもとに状態を保管するStoreを作りましょう。これによって、Reducerによって新しく作成された状態(Store)をAppコンポーネントに渡すことができます。

```react:src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import {createStore} from 'redux';
import {Provider} from 'react-redux';

import reducer from './reducers';

const store = createStore(reducer);

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

##ReactとReduxを結合させる
ここでは`App.js`に`mapStateToProps`と`mapDispatchToProps`を追加し、`connect`で結合させましょう！

`mapStateToProps`
Storeが持つ状態(state)をどのようにpropsに混ぜ込むかを決める

`mapDispatchToProps`
Reducer にアクションを通知する関数dispatchをどのようにpropsに混ぜ込むかを決める

`bindActionCreators()`
Action を追加するたびにconnectに追加するのは大変なのでbindActionCreators を使って自動的にマッピングする


```react:src/App.js
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import './App.css';

import * as actions from './actions';
import NumBtn from './components/NumBtn';
import OperatorBtn from './components/OperatorBtn';
import Result from './components/Result';

class App extends Component {
  render() {
    const {calculator, actions} = this.props;
    console.log(calculator);
    console.log(actions);

    return (
      <React.Fragment>
        <div className="result">
          <Result
            result={
              calculator.showingResult
                ? calculator.resultValue
                : calculator.inputValue
            }
          />
        </div>
        <div className="wrapper">
          <div className="number">
            <div className="numUpper">
              <NumBtn n={7} onClick={() => actions.onNumClick(7)} />
              <NumBtn n={8} onClick={() => actions.onNumClick(8)} />
              <NumBtn n={9} onClick={() => actions.onNumClick(9)} />
            </div>
            <div className="numMiddle">
              <NumBtn n={4} onClick={() => actions.onNumClick(4)} />
              <NumBtn n={5} onClick={() => actions.onNumClick(5)} />
              <NumBtn n={6} onClick={() => actions.onNumClick(6)} />
            </div>
            <div className="numLower">
              <NumBtn n={1} onClick={() => actions.onNumClick(1)} />
              <NumBtn n={2} onClick={() => actions.onNumClick(2)} />
              <NumBtn n={3} onClick={() => actions.onNumClick(3)} />
            </div>
            <div className="zero">
              <NumBtn n={0} />
              <span className="allClear">
                <OperatorBtn o={'AC'} onClick={() => actions.onClearClick()} />
              </span>
              <span className="equal">
                <OperatorBtn o={'='} onClick={() => actions.onEqualClick()} />
              </span>
            </div>
          </div>
          <div className="operator">
            <OperatorBtn o={'÷'} onClick={() => actions.onDivideClick()} />
            <OperatorBtn o={'×'} onClick={() => actions.onMultiplyClick()} />
            <OperatorBtn o={'-'} onClick={() => actions.onMinusClick()} />
            <OperatorBtn o={'+'} onClick={() => actions.onPlusClick()} />
          </div>
        </div>
      </React.Fragment>
    );
  }
}

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

const mapDispatchToProps = dispatch => {
  return {
    actions: bindActionCreators(actions, dispatch),
  };
};

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);
```

最後にComponentも変更します。

```react:src/components/NumBtn
import React from 'react';

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

export default NumBtn;
```

```react:src/components/OperatorBtn.js
import React from 'react';

const OperatorBtn = ({o, onClick}) => <button onClick={onClick}>{o}</button>;

export default OperatorBtn;
```

以上で完成です。

電卓としての機能はまだ不十分なところもございますので、この記事を参考にしつつ新しい機能をつけていただけたら幸いです！

#リファレンス
- [電卓アプリで学ぶReact/Redux入門(実装編)](https://qiita.com/nishina555/items/9ff744a897af8ed1679b)
- [複数の Action Creators を bindActionCreators() でまとめてバインドする(React/Redux)
](https://noah.plus/blog/024/)
- [bindActionCreators(actionCreators, dispatch)](https://redux.js.org/api/bindactioncreators)
- [たぶんこれが一番分かりやすいと思います React + Redux のフロー図解]()