React+Reduxを楽に書きたい

素の書き方や構成だと継続開発の意思が折れそうなので楽な形を試してみる

# 題材
ただの足し算
![sum.gif](https://qiita-image-store.s3.amazonaws.com/0/82438/030ab247-26a3-5366-343f-b7bbd514787f.gif)

# ファイル構成

<img width="363" alt="tree.png" src="https://qiita-image-store.s3.amazonaws.com/0/82438/62bf6dcf-d39c-2cfd-643f-c45640abd7cb.png">

# modules/sum.jsx

Ducksパターンで action,reducer,constants を1ファイルにします
redux-actions を使って記述を短縮し、Actionの構造を固定します

```jsx
import { createActions, handleActions } from 'redux-actions';

// action type
const SET_ITEM1 = 'SET_ITEM1'
const SET_ITEM2 = 'SET_ITEM2'

// action-creator
export const actions = createActions(
  SET_ITEM1,
  SET_ITEM2
)

const defaultState = {
  item1: 10,
  item2: 0,
  sum: 10
}

// reducer
export default handleActions(
  {
    [SET_ITEM1]: (state, { payload: item1 }) => ({
      ...state,
      item1: item1,
      sum: state.item2 + item1
    }),
    [SET_ITEM2]: (state, { payload: item2 }) => ({
      ...state,
      item2: item2,
      sum: state.item1 + item2
    })
  },
  defaultState
)
```

# reducer.jsx

機能追加を考慮して combineReducers で結合します

```jsx
import { combineReducers } from 'redux'
import sum from './modules/sum'

export default combineReducers({
  sum
})
```

# store.jsx

変わった記述はありません

```jsx
import { createStore } from 'redux'
import reducer from './reducer'
export default createStore(reducer)
```

# components/sum.jsx

関数や状態はcontainer部分に任せ、React描画は描画だけやる
container部分は複雑になったらファイル分割すると良いらしい

```jsx
import React from 'react'
import { connect } from 'react-redux'
import { actions } from '../modules/sum'
import { withStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardContent from '@material-ui/core/CardContent';
import Typography from '@material-ui/core/Typography';
import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel';
import NativeSelect from '@material-ui/core/NativeSelect';
import FormControl from '@material-ui/core/FormControl';

const styles = theme => ({
  formControl: {
    margin: theme.spacing.unit
  }
});

// 描画だけなら React.component を継承したクラスは不要
const Sum = (props) => {
  const { classes, item1, item2, sum, setItem1, setItem2 } = props
  return (
    <Card>
      <CardContent>
        <Typography>
          {item1} + {item2} = {sum}
        </Typography>
        <FormControl className={classes.formControl}>
          <InputLabel htmlFor="item1">左辺</InputLabel>
          <NativeSelect
              value={item1}
              onChange={setItem1}
              input={<Input name="item1" id="item1"/>}
            >
              <option value={10}>Ten</option>
              <option value={20}>Twenty</option>
              <option value={30}>Thirty</option>
            </NativeSelect>
          </FormControl>
          <FormControl className={classes.formControl}>
            <InputLabel htmlFor="item2">右辺</InputLabel>
            <NativeSelect
              value={item2}
              onChange={setItem2}
              input={<Input name="item2" id="device2"/>}
              >
              <option value={0} />
              <option value={10}>Ten</option>
              <option value={20}>Twenty</option>
              <option value={30}>Thirty</option>
            </NativeSelect>
          </FormControl>
      </CardContent>
    </Card>
  )
}

// container
// state肥大化対策: combineReducersでstateも分割されるので必要な部分だけ参照する
const mapStateToProps = state => (state.sum)
const mapDispatchToProps = dispatch => ({
  setItem1: (e) => dispatch(actions.setItem1(Number(e.target.value))),
  setItem2: (e) => dispatch(actions.setItem2(Number(e.target.value)))
})

export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(Sum))
```

# app.jsx

変わった記述はありません

```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import Sum from './components/sum'
import store from './store'
import { Provider } from 'react-redux'

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.render(
    <Provider store={store}>
      <Sum />
    </Provider>,
    document.body.appendChild(document.createElement('div')),
  )
})
```

# 結果

部品が2ファイルで作れて、冗長な記述が消えてきた、これなら継続開発できそう

# 補足: state肥大化

そもそもReduxで管理するような値ではないとか [ReduxにおけるGlobal stateとLocal stateの共存](https://made.livesense.co.jp/entry/2016/09/29/080000) とかの話はありますが、部品毎に書き方が変わったり、使い分けの境目を検討したりというのが「楽」ではないと思ったので、ここでは考えません

またcombineReducersでstateも分割されるので、肥大化してもある程度は誤魔化せそうだとも思っています

```jsx
const mapStateToProps = state => {
  console.log(state)
  return state.sum
}
```
<img width="363" alt="state.png" src="https://qiita-image-store.s3.amazonaws.com/0/82438/125105f3-6ce7-ba2e-baa2-1111fe4ede8d.png">


# 参考

1. [The Ducks File Structure for Redux](https://medium.com/@scbarrus/the-ducks-file-structure-for-redux-d63c41b7035c)
2. [[React] Ducks というデザインパターン [Redux]](https://webbibouroku.com/Blog/Article/redux-ducks)