今から学ぶReduxの基礎（実践編）

## Reduxを使ってみる

Reduxを使って以下のようなカウントアップする簡単なアプリケーションを作成していきます。
カウントを行うコンポーネントが2つ存在し、propsで受け渡すことなく状態を共有しています。
![redux-sample.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315789/ccb76e30-535f-9682-1a27-90f047b48943.gif)


### 事前準備

サンプルアプリの作成とReduxのインストールから行います。

```shell
// アプリケーション作成
npx create-react-app redux-practice

// Reduxインストール
yarn add redux react-redux
```

### Redux Hooks

まずはHooksを使用した書き方で作成します。
アプリケーションの名前は「redux-practice」とします。
以下のようなファイル構成でファイルを作成します。

```
redux-practice
├── public
└── src
    ├── actions
    │   └── index.js
    ├── components
    │   ├── Another.js
    │   └── App.js
    └── reducers
    │   └── index.js
    └── index.js
```

次に以下のようにactions/index.jsを編集していきます。
今回はカウンターアプリなので「increment」と「decrement」というアクションを作成しています。

```actions/index.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

export const increment = () => {
  return {type: INCREMENT}
}

export const decrement = () => {
  return {type: DECREMENT}
}
```

続いて、reducers/index.jsを以下のように編集します。
こちらは、先ほど定義したActionと最新のstateを自動的に受け取って新しいstateを返しています。

```reducers/index.js
import { INCREMENT, DECREMENT } from '../actions'
const initialState = { value: 0 }

const count = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { value: state.value + 1 }
    case DECREMENT:
      return { value: state.value - 1 }
    default:
      return state
  }
}

export default
```

次にcomponents/App.jsで表示部分を以下のように作成していきます。
以下では「+」ボタンが押された時に「increment」、「-」ボタンが押された時に「decrement」というイベントを発行するようにしています。

```components/App.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { increment, decrement } from '../actions'

const App = () => {
  const value = useSelector((state) => state.value)
  const dispatch = useDispatch();
  return (
    <>
      <div>count:{value}</div>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </>
  );
}

export default App
```

最後に他のコンポーネントからstateを参照してみましょう。
components/Another.jsでuseSelectorを使うことで、stateの値を参照することができます。

```components/Another.js
import React from 'react'
import { useSelector } from 'react-redux'

const Another = () => {
  const value = useSelector((state) => state.value)

  return (
      <div>AnotherComponentCount:{value}</div>
  );
}

export default Another
```

### ReduxがHooksに対応する前の書き方

先ほどはHooksを使用した書き方を紹介しましたが、
古いコードではHooksを使用しない書き方を使っていることもあるので、
先ほどのコードを古いコードで書き換えてみます。

```components/App.js
import React from 'react'
import { connect } from 'react-redux'
import { increment, decrement } from '../actions'

const App = (props) => {
  return (
    <>
      <div>count:{props.value}</div>
      <button onClick={props.increment}>+</button>
      <button onClick={props.decrement}>-</button>
    </>
  );
}

// stateから必要な情報をコンポーネントにマッピングする関数
const mapStateToProps = state => ({ value: state.count.value })

// dispatch関数をコンポーネントにマッピングする関数
const mapDispatchToProps = dispatch => ({
  increment: () => dispatch(increment()),
  decrement: () => dispatch(decrement())
})

// 指定したコンポーネントのpropsにstateを混ぜ込む
export default connect(mapStateToProps, mapDispatchToProps)(App)
```

```components/Another.js
import React from 'react'
import { connect } from 'react-redux'

const Another = (props) => {
  return (
      <div>AnotherComponentCount:{props.value}</div>
  );
}

// stateから必要な情報をコンポーネントにマッピングする関数
const mapStateToProps = state => ({ value: state.count.value })

// 指定したコンポーネントのpropsにstateを混ぜ込む
export default connect(mapStateToProps)(Another)
```

connectという関数を使う必要があるため、少し冗長になってしまいます。。。

### Redux Toolkitを使う

ここまでReduxのコードを見てきて感じたかもしれませんが、Reduxを使うとコード量が増えます。
その課題を解決するのがRedux Toolkitです。
実際にRedux Toolkitでコードを書いていきましょう。

ファイル構成は以下

```
redux-practice
├── public
└── src
    ├── components
    │   ├── Another.js
    │   └── App.js
    └── counter
    │   └── counter.js
    └── index.js
```

まず、counter/counter.jsを以下のように編集します。

```counter/counter.js
import { createSlice } from '@reduxjs/toolkit'

const initialState = { value: 0 }

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => ({...state, value: state.value + 1}),
    decrement: (state) => ({...state, value: state.value - 1})
  }
})
```

Redux Toolkitで提供されているcreateSlice関数を使用すると、ActionとReducerをまとめて記述することができます。

components/App.jsとcomponents/Another.jsは以下のように編集します。
（ここら辺は以前とほぼ同じです）

```components/App.js
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { counterSlice } from '../counter/counter';

const App = () => {
  const value = useSelector((state) => state.value)
  const dispatch = useDispatch();
  const { increment, decrement } = counterSlice.actions;
  return (
    <>
      <div>count:{value}</div>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </>
  );
}

export default App
```

```components/Another.js
import React from 'react'
import { useSelector } from 'react-redux'

const Another = () => {
  const value = useSelector((state) => state.value)

  return (
      <div>AnotherComponentCount:{value}</div>
  );
}

export default Another
```

最後にindex.jsを編集して終わりです。

```index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { configureStore } from '@reduxjs/toolkit'
import App from './components/App'
import Another from './components/Another'
import {counterSlice} from './counter/counter'

const store = configureStore({reducer: counterSlice.reducer})

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

### useReducerとuseContextを使って、Reduxのようなグローバルステートを実現する

### useReducerとuseContext

これまでReduxを使用してグローバルステートを実現していましたが、
HooksのuseReducerとuseContextを使ってReduxのようにグローバルステートを実現することができます。

ではuseReducerとuseContextでコードを書いていきましょう。

ファイル構成は以下

```
redux-practice
├── public
└── src
    ├── actions
    │   ├── index.js
    ├── components
    │   ├── Another.js
    │   └── App.js
    └── counter
    │   └── counter.js
    └── index.js
```

まず、actions/index.jsは以下のように編集します。
（最初のコードと同じです）

```actions/index.js
export const INCREMENT = 'INCREMENT'
export const DECREMENT = 'DECREMENT'

export const increment = () => {
  return {type: INCREMENT}
}

export const decrement = () => {
  return {type: DECREMENT}
}
```

続いて、counter/counter.jsを以下のように編集します。
（こちらも最初のコードとほぼ同じです）

```counter/counter.js
export const counterReducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return {value: state.value + 1}
    case "DECREMENT":
      return {value: state.value - 1}
    default:
      return state
  }
}
```

次にcomponents/App.jsを以下のように編集します。

```components/App.js
import React, { createContext, useReducer } from "react";
import { counterReducer } from '../counter/counter'
import Another from './Another'
import { increment, decrement } from "../actions";

export const CounterContext = createContext();

const App = () => {
  const initialState = { value: 0 }
  const [state, dispatch] = useReducer(counterReducer, initialState);
  return (
    <>
      <CounterContext.Provider value={{state, dispatch}}>
        <div>count:{state.value}</div>
        <button onClick={() => dispatch(increment())}>+</button>
        <button onClick={() => dispatch(decrement())}>-</button>
        <Another/>
      </CounterContext.Provider>
    </>
  );
}

export default App
```

ここでのポイントは以下の3つです。

- useReducerの引数にReducerとstateの初期値を渡し、その返り値として最新のstateとdispatchを受け取っている

- CounterContextという定数を定義し、CounterContext.Providerのvalue属性にグローバルで扱いたいstateを設定

- CounterContext.Providerコンポーネントに囲まれているコンポーネントは、設定したstateを使用できる


上記のようにReact Hooksを使えばReduxのように状態管理を行えるようになります。