日本一わかりやすいReact-Redux入門#8~#10 学習備忘録

## はじめに
この記事は、Youtubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『[日本一わかりやすいReact-Redux入門](https://www.youtube.com/watch?v=FBMA34gUsgw&t=109s)』の学習備忘録です。

前回の記事は[こちら](https://qiita.com/ddpmntcpbr/items/f4b82894e8aa0c48ced0)。

## 要約
- Reduxファイルは、``reducksパターン``で管理すると、開発・運用・保守全ての面で効率的になる
- ``selectors.js``にセレクター関数を定義することで、Store内のstateの値を、任意のコンポーネントで簡単に参照・取得できる。
- 外部API、DBとの通信時には、``redux-thunk``による非同期処理制御を入れる
- Redux store からの state の取り出し方は「1.コンテナーコンポーネント」「2.``React-Hooks``」の二通りあるが、基本的には後者を採用するべき

## #8...re-ducksパターンでファイル管理をしよう

### re-ducksパターンとは？
Redux 関連ファイルの**ディレクトリ構成パターン**。ファイルの分割基準をルールとして決めてしまうことで、ファイルを管理しやすくする、というもの。

### ディレクトリ構成

```
reducks
 ├ users 
 ├ products
 ⋮
```

state ごとにディレクトリを分けます。

### ファイル構成
```
users
 ├ actions.js
 ├ index.js
 ├ operation.js
 ├ reducers.js
 ├ selectors.js
 └ type.js
```

state の名前によらず、これら Redux 関連ファイルのファイル名は統一します。

各ファイルの役割をまとめます。

#### actions.js
Fluxフローにおける最初の窓口。アプリから受け取った state の変更依頼を受け取り、 ``reducers.js`` に渡す。

#### operations.js
``actions.js`` の前に実行したい「何らかの複雑な処理」を書くための場所(例：外部APIやDBから値を取得する、等)

このファイルを用意することで、``actions.js``や``reducers.js``の記述をシンプルかつ画一的に保つことができるようになる。次回動画以降登場。

#### reducers.js
``actions.js`` からデータを受け取り、 Store の state をどう変更するか決める。

#### types.js
Typescript 使用時のみ作成する。型定義を記述して export する。

#### selectors.js
Store で管理している state を参照する関数を定義して export する。

「どこに何を書くべきか」を決めておくことで、開発スピードアップだけでなく、保守・運用時の手間も最小化できそうです。

### selectors.jsの使い方
今回は、``selectors.js``を作成して、state の参照を実行してみます。

```javascript:src/reducks/users/selectors.js
import { createSelector } from "reselect";

const usersSelector = (state) => state.users;

export const getUserId = createSelector(
  [usersSelector],
  state => state.uid
)
```

``getUserId``という関数を定義しています。これで、Store の中で管理されている state のうち、 users.uid を、任意のコンポーネントで参照・取得できます。

早速、 Home.jsx で使ってみます。

```react:src/templates/Home.jsx
import React from 'react';
import {getUserId} from '../reducks/users/selectors';
import {useSelector} from 'react-redux'

const Home = () => {
  const selector = useSelector(state => state);
  const uid = getUserId(selector);

  return (
    <div>
      <h2>Home</h2>
      <p>{uid}</p>
    </div>
  );
};

export default Home
```

``useSelector()``は ``React Hooks``の一種で、Store 全体の state を受け取ります。。これを``getUserId()``に渡すことで、 uid を取り出せます。

[localhost:3000](http://localhost:3000/) をみてみると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/810a5fa3-e982-62da-1bb1-a9c94ad1fa2a.png)

問題なく取得ができています（0000 は ``initialState.js``で定義した user.uid)

さらに、既に定義してある ``signInAction`` を用いて、Store 内の state を更新し、``getUserId()``で正しく参照できるかを試してみます。

```react:src/templates/login.jsx
import React from 'react';
import {useDispatch} from "react-redux";
import {push} from "connected-react-router";
import {signInAction} from "../reducks/users/actions"

const Login = () => {
  const dispatch = useDispatch();
  return (
    <div>
      <h2>ログイン</h2>
      <button onClick={() => {
        dispatch(signInAction({uid:"0001", username: "torahack"}))
        dispatch(push('/'))}} >
        ログイン
      </button>
    </div>
  );
};

export default Login
```

``<button>`` をクリックすることで``signInAction``が発火し、state.user が更新されるはずです、。

[http://localhost:3000/login](http://localhost:3000/login) より、ボタンをクリックすると、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/89cd0077-1ef4-1a18-1674-cd2c0fa51266.png)
↓
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/53b60399-ef76-5f2f-1aed-09bd73e57e02.png)


無事、state.user.uid が更新が確認できます！

## #9...redux-thunkで非同期処理を制御すべし

### 非同期処理とは？
``時間のかかる処理``と並行して、次の処理を進めてしまうこと。

``時間のかかる処理``とは、例えば外部APIとのリクエスト・レスポンスの処理や、データベースとの通信などを指す。

通常の React では”時間のかかる処理”が完了する前に次の処理がどんどん進んでいきます(非同期で処理が進む)。しかし例えば「データベースへクエリを出し、返ってきた結果を Redux の Store に保存する」といった場面では、結果が返ってくるまでは処理を止めておかなければ、正常は画面描画を行えません。

### redux-thunk とは？
React で非同期処理を制御するためのライブラリ。

``actions.js``から``reducers.js``へフローを渡すタイミングを制御できます。通常、redux-thunk の記述は、``operations.js``に書くケースが多いです。

### redux-thunk を導入

``store.js``に、redux-thunk を導入します。


```react:src/reducks/store/store.js
⋮
import thunk from "redux-thunk";

export default function createStore(history) {
    ⋮
    applyMiddleware(
      routerMiddleware(history),
      thunk
    )
  )
}
```

たった2行追加するだけでOK。次に、``operations.js``を追加します。

```react:src/reducks/users.operations.js
import { signInAction } from "./actions";
import { push } from "connected-react-router";

export const signIn = () => {
  return async (dispatch, getState) => {
    const state = getState()
    const isSignedIn = state.users.isSignedIn

    if(!isSignedIn) {
      // 実際は以下にfirebaseと通信をするようなサインイン処理を書くが、
      // まだ実装していないのでダミー処理を書く
      const url = 'https://api.github.com/users/deatiger'

      const response = await fetch(url)
                            .then(res => res.json())
                            .catch(() => null)

      const username = response.login

      dispatch(signInAction({
        // state.user に対する変更内容
        isSignedIn: true,
        uid:"0002",
        username: username,
      }))
      // 上記処理後、ルートへリダイレクト
      dispatch(push('/'))
    }
  }
}
```

- ``async``と書くことで、``await``(その処理が完了するまで次の処理に進まない)を使える
- ``getState()``で store から state を取得できる。
- ``dispatch()``で actions および push メソッドを使用できる。

``if(!isSignedIn){...`` 以下は、本来であればバックエンド( fireabase など)との通信を行い、ユーザー認証の結果を action へ渡すことで、store 内の state の変更を行います。

今回はバックエンド側は未実装なので、ダミーとして ユーザー名``deatiger``のgithub APIを叩く仕様にしています。正常に動けば、該当ユーザーのユーザー名を取得し、それを username に格納するよう、action へ命令を出します。

この``operations.js``を使用できるように、templates に変更を加えます。今回は``Login.jsx``内のログインボタンを押した時に``operations.js``が発火し、記述した通りの state の変更がなされるように記述します。

```react:src/templates/Login.jsx
import React from 'react';
import {useDispatch} from "react-redux";
import {signIn} from "../reducks/users/operations"

const Login = () => {
  const dispatch = useDispatch();
  return (
    <div>
      <h2>ログイン</h2>
      <button onClick={() => dispatch(signIn())} >
        ログイン
      </button>
    </div>
  );
};

export default Login
```

``<button>``タグの onClick イベントとして 先ほどの ``operations.js``をセットしています。上手くいけば、 state.user の情報が更新されたのち、ルートへリダイレクトされるはずです。


[http://localhost:3000/login](http://localhost:3000/login)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/a6eecbe6-7513-db8d-4477-5c117991fa7c.png)

↓ ログインボタンをクリックすると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/5d729936-6ff4-5947-87bc-922df808e489.png)

``operations.js``の記述の通り、uid が 0002 へ変更されています！

ついでに、この画面(``Home.jsx``)で username も表示させてみます。templates ファイルでStore内のstateを取得するためには、``selectors.js``で、 username を取得する関数を定義する必要があります。

```react:reducks/users/selectors.js
import { createSelector } from "reselect";

const usersSelector = (state) => state.users;

export const getUserId = createSelector(
  [usersSelector],
  state => state.uid
)

// 以下追記
export const getUserName = createSelector(
  [usersSelector],
  state => state.name
)
```

state.user.username を取得する関数として ``getUserName()``を定義します。

```react:src/templates/Home.jsx
import React from 'react';
import {getUserId, getUserName} from '../reducks/users/selectors';
import {useSelector} from 'react-redux'

const Home = () => {
  const selector = useSelector(state => state);
  const uid = getUserId(selector);
  const username = getUserName(selector);

  return (
    <div>
      <h2>Home</h2>
      <p>{uid}</p>
      <p>ユーザー名: {username}</p>
    </div>
  );
};

export default Home
```

``getUserName()``を import して使います。ブラウザで確認しましょう。


[http://localhost:3000/](http://localhost:3000/)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/081e6227-b05f-36f7-4ec9-c6c9c3a3112f.png)

↓ localhost:3000/login でボタンを押すと、


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/65da01b1-3af3-b1d0-83b9-10b10f0b363e.png)


username も変更されています！

もし今回の処理を redux-thunk による非同期処理制御を入れなかった場合、github api よりユーザー情報を取得する処理の完了を待たずに action の発行に進んでしまうため、ユーザー情報がうまく画面が表示されなくなってしまいます。

「外部APIやDBとの通信を行う際には非同期制御を入れる」と覚えておけば、大体のケースには対応できそうです。

## #10...コンテナーの役割

### コンテナーコンポーネントとは
Store とコンポーネントの中継役。 Redux(Store) の世界と React(アプリ) の世界をつなぐ。

かつては更新された Store 内 state を アプリに渡すために唯一の手段でしたが、現在は``Redux-Hooks``を用いることでも Store 内 stateを渡せるようになりました。

基本的には``Redux-Hooks``の方が記述が少なくて楽なため、コンテナーコンポーネントを使う場面は限られています。

### いつ使うべき？
明示的に state をフィルタリングしたいに使用します。

例えばユーザー認証情報など、セキュリティの関連から state を渡すコンポーネントを最小限に抑えたいときなどで、コンテナーコンポーネントがしばしば用いられます。

また、「``React Hooks``が登場する以前に書かれた React+Reduxコードを理解するために、知識としては持っておくべき」という観点も、学習するモチベーションと言えます。

### connect() の使い方

コネクトコンポーネントは``src/containers/``の下に保存します。

今回は、``React Hooks``で実装していた、Login Component への state の引き渡しを、コネクトコンポーネントで実装してみます。

実装ファイルは、以下の5ファイル。

```
1. src/containers/Login.js (コンテナーコンポーネント)
2. src/templates/LoginClass.jsx (クラスコンポーネントで実装したログインコンポーネント。コンテナーコンポーネントからの state を受け取るためには、関数コンポーネントではなく、クラスコンポーネントである必要がある)
3. src/containers/index.js
4. src/Route.jsx (/login の読み込み先を、LoginClassコンポーネントへ変更)
```

```react:src/containers/Login.js
import LoginClass from '../templates/LoginClass'
import {compose} from 'redux'
import {connect} from 'react-redux';
import * as Actions from '../reducks/users/operations';

const mapStateToProps = state => {
  return {
    users: state.users // 渡したい state だけをオブジェクト型で記述
  }
}

const mapDispatchToProps = dispatch => {
  return {
    actions: {
      signIn() {
        dispatch(Actions.signIn()) // Store から Dispatch する関数
      }
    }
  }
}
export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )
)(LoginClass)
```

コネクトコンポーネントで state を渡されるコンポーネントは、クラスコンポーネントがある必要があります。Login Component をクラスコンポーネントで定義し直したものを用意します。

```react:src/templates/LoginClass.jsx
import React, {Component} from 'react';

export default class LoginClass extends Component {
  render() {
    return (
      <div>
      <h2>ログイン</h2>
      <button onClick={() => dispatch(signIn())} >
        ログイン
      </button>
    </div>
    )
  }
}
```

```react:src/templates/LoginClass.jsx
import React, {Component} from 'react';

export default class LoginClass extends Component {
  render() {
    return (
      <div>
      <h2>ログイン</h2>
      <button onClick={() => this.props.actions.signIn()} >
        ログイン
      </button>
    </div>
    )
  }
}
```

```react:src/containers/index.jsx
export {default as LoginContainer } from './Login'
```

```react:src/Route.jsx
import React from 'react';
import {Route, Switch} from "react-router";
import {Login, Home} from "./templates";
import {LoginContainer} from "./containers"

const Router = () => {
  return (
    <Switch>
      {/* <Route exact path={"/login"} component={Login} /> */}
      <Route exact path={"/login"} component={LoginContainer} />
      <Route exact path={"(/)?"} component={Home} />
    </Switch>
  );
};

export default Router
```

ここまで実装することで、#9の最後と同じブラウザ表示を確認することができるはずです。

コンテナーコンポーネントによる実装は、``Redux Hooks``に比べ記述量が多く、かつファイル数も増えてしまいます。

知識としては持っておくべきですが、特別な事情がない限りは``Redux Hooks``を使用すべきでしょう。

### おわり
今回記事を要点をまとめると、

- Reduxファイルは、``reducksパターン``で管理すると、開発・運用・保守全ての面で効率的になる
- ``selectors.js``にセレクター関数を定義することで、Store内のstateの値を、任意のコンポーネントで簡単に参照・取得できる。
- 外部API、DBとの通信時には、``redux-thunk``による非同期処理制御を入れる
- Redux store からの state の取り出し方は「1.コンテナーコンポーネント」「2.``React-Hooks``」の二通りあるが、基本的には後者を採用するべき

です。

今回はここまで！次回からは実践編として、実際にECアプリの開発を通じた学習が始まる予定です。