ステップバイステップで Redux で非同期処理を行う React コンポーネントを作る

## 概要

1日 Redux を触って既存のコンポーネントを書き換えていきました。サンプルやドキュメントはいきなり完成形のコンポーネントについて説明するものが多く分かりづらかったので、忘備録としてここにまとめます。

ステップバイステップで単純なコンポーネントから Redux で非同期処理を行うコンポーネントを作っていきます。

Redux のセットアップや store の作り方などは触れません。

## 1. Stateless Component を作る

固定の情報を表示するだけのものを作ります

```js:HelloComponent.js
export default function HelloComponent(props) {
  return <p>Hello, {props.name}!</p>
}

HelloComponent.propTypes = {
  PropTypes.string.isRequired
}
```

## 2. Redux と接続する

一枚岩 state から必要な情報を加工して props に変換する `mapStateToProps` を追加します。

`connect()` に渡して Redux と接続されたコンポーネントを作ります。

```js:HelloComponent.js
function HelloComponent(props) {
  return <p>Hello, {props.name}!</p>
}

HelloComponent.propTypes = {
  PropTypes.string.isRequired
}

// ログイン済みユーザーの情報 state.user からユーザー名を取り出して props.name に変換する
function mapStateToProps(state) {
  return {
    name: state.user && state.user.name
  }
}

export default connect(mapStateToProps)(HelloComponent)
```

## 3. イベントハンドリング

押した時にログアウトを行うボタンを追加します

### Action を作成する

ログアウトする Action Creator を作ります。

- アクションを識別する type の文字列
- アクションを生成する関数 (Action Creator)

```js:actions.js
export const SET_USER = "SET_USER"

// Action Creator
export function setUser(user) {
  return {
    type: SET_USER,
    user: user
  }
}
```

Action はこの Action Creator が返すオブジェクトのことです

### Reducer を作成する

SET_USER に対応する state の遷移を実装します

```js:reducers.js
import { combineReducers } from "redux"
import { SET_USER } from "./actions"

function user(state = null, action) {
  switch(action.type) {
    case SET_USER:
      return action.user
    default:
      return state
  }
}

export default combineReducers({
  user
})
```

### Component 側の変更

ボタンを押した時に state.user を null にしてログアウトしたことにします。
onClick のようなイベントからアクションを生成する `mapDispatchToProps` を追加します。

```js:HelloComponent.js
import { setUser } from "./actions"

function HelloComponent(props) {
  return <div>
    <p>Hello, {prop.name}!</p>
    <button onClick={props.onClickLogout}>Logout</button>
  </div>
}

HelloComponent.propTypes = {
  PropTypes.string.isRequired
}

function mapStateToProps(state) {
  return {
    name: state.user && state.user.name
  }
}

function mapDispatchToProps(dispatch) {
  return {
    onClickLogout: () => { dispatch(setUser(null)) }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(HelloComponent)
```

## 4. 非同期処理

一番簡単だと思った `redux-thunk` ミドルウェアによる方法を説明します。

### Action の変更

ログアウト API を叩き、成功時に setUser() で null をセットする非同期 Action を追加します。
非同期 Action は dispatch を引数にとる関数を返す関数として定義されます。

この関数は `redux-thunk` ミドルウェアが導入されていない場合エラーになります。

```js:actions.js
export const SET_USER = "SET_USER"

// Action Creator
export function setUser(user) {
  return {
    type: SET_USER,
    user: user
  }
}

export const logout = () = dispatch => {
  exampleApi.logout(error => {
    if (!error) {
      dispatch(setUser(null))
    }
  })
}
```

### コンポーネントの変更

setUser(null) を logout() に変えます。

```js:HelloComponent.js
import { logout } from "./actions"

function HelloComponent(props) {
  return <div>
    <p>Hello, {prop.name}!</p>
    <button onClick={props.onClickLogout}>Logout</button>
  </div>
}

HelloComponent.propTypes = {
  PropTypes.string.isRequired
}

function mapStateToProps(state) {
  return {
    name: state.user && state.user.name
  }
}

function mapDispatchToProps(dispatch) {
  return {
    onClickLogout: () => { dispatch(logout()) }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(HelloComponent)
```

## 所感

Redux を始めたばかりですが、思ったより地道に書き換える事ができてよかったです。非同期処理まわりはいろんな方法があるそうなので、規模に合わせていい感じの書き方を学んでいきたいと思います。