React + Redux アプリのデータの流れを説明する

# この記事は

React + Redux を使ったアプリケーションのデータの流れについて解説しています。

# 誰に向けた記事か

React + Redux をまだ使用したことがない or 経験がまだ少ない人。

# React + Redux アプリのデータの流れ

## React のコンポーネント

コンポーネントのコードは以下のようになっています。

```jsx
const ProductList = () => (
  <div>
    <h1>商品リスト</li>
    <ul>
      <li>Nintendo Switch</li>
      <li>PlayStation</li>
      <li>Xbox</li>
    </ul>
  </div>
);
```

イメージにするとこのような木構造になっているとお考えください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/b582d400-edb7-9605-ea43-52723a7ac7be.png)

## state と props

コンポーネントで扱うデータには state と props があります。
state はコンポーネント内部に持っているデータで、props は親から子に渡されるデータです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/4e4df0cb-1083-8cf8-a43c-665e68aec40f.png)

以下の例では `title` が state で、`listTitle` が props になります。

```jsx
// これが親コンポーネント
const Product = () => {
  // これがstate
  const [title, setTitle] = useState("商品リスト");
  // HTMLの属性のようにして渡しているのがprops
  return <ProductList listTitle={title} />;
};

// これが子コンポーネント
const ProductList = (props) => (
  <div>
    <h1>{props.listTitle}</li>
    <ul>
      <li>Nintendo Switch</li>
      <li>PlayStation</li>
      <li>Xbox</li>
    </ul>
  </div>
);
```

props にはデータだけでなくコールバック関数を渡すことができます。
この関数をイベントハンドラとして onClick などに設定することで、親のコンポーネントに value を渡すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/a0b5b44e-9ded-1c4d-93cc-169354f5f0ee.png)

以下の例では子コンポーネントにコールバック関数を渡し、親の state を更新しています。

```jsx
// これが親コンポーネント
const Product = () => {
  // これがstate、setTitleはtitleを変更するための関数
  const [title, setTitle] = useState("商品リスト");
  // HTMLの属性のようにして渡しているのがprops
  // handleClickのようにコールバック関数を渡すこともできる
  return <ProductList listTitle={title} handleClick={setTitle} />;
};

// これが子コンポーネント
const ProductList = (props) => (
  <div>
    <h1>{props.listTitle}</li>
    <ul>
      <li>Nintendo Switch</li>
      <li>PlayStation</li>
      <li>Xbox</li>
    </ul>
    <button onClick={() => props.handleClick("ゲーム機")}>タイトル変更</button>
  </div>
);
```

## render とライフサイクル関数

props が更新されると、それを受け取ったコンポーネントのツリーが自動的に更新されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/62ca34df-615f-a886-3508-bdaddd5f84fe.png)

具体的にはコンポーネントの render 関数やライフサイクル関数が再実行されます。
ライフサイクルに関して、公式サイトからリンクされていた図があるので、ご参照ください。

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

## 横断的関心事

別々のコンポーネントで同じデータを使いたい場合があったとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/ffb3421b-f6bc-89b0-a6b0-aad2bf1ff39e.png)

その場合は、共通の親コンポーネントから props をリレーのように渡す必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/46a83e69-a051-4f42-bd54-2ee4adc06e6e.png)

しかしこれはあまりスマートではないですよね？

Redux を使うとこの問題に対処できます。
Redux では Store と呼ばれる共有データストアから、直接コンポーネントに props を渡すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/599afde7-4f67-2561-4c1b-2a919b0aaf50.png)

## Redux の仕組み

Store からデータを props に渡すために、container と呼ばれるコンポーネントでラップします。
container コンポーネントでは props のデータに Store の一部のデータ、コールバック関数に dispatch と呼ばれる関数をマッピングします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/be403c09-a2ba-b55c-6314-1a03724b4a91.png)

connect という関数を使って Store のデータと dispatch をマッピングします。

```js
// Storeのどのデータをpropsにマッピングするか
const mapStateToProps = (state) => ({
  isLogin: state.Auth.isLogin,
});

// LoginPageコンポーネントのpropsには、isLoginとdispatch関数が渡される
export default connect(mapStateToProps)(LoginPage);
```

## Action

Store のデータを更新したい場合は、コンポーネントから dispatch 関数に対して Action と呼ばれる JavaScript オブジェクトを渡します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/907e2199-27d4-e24b-9edf-cb51ff36a034.png)

以下のように、コンポーネントの onClick などから実行します。
ですが、この実装方法だと、type を間違えるなどのミスの原因になりそうですね。

```jsx
const Button = (props) => (
  <button onClick={() => props.dispatch({ type: "login" })}>ログイン</button>
);
```

そのため、通常は ActionCreator と呼ばれる関数を定義して共用します。

```js
// Action Type
export const LOGIN = "Auth/LOGIN";

// Action Creator
export function login() {
  return { type: LOGIN };
}
```

```js
const Button = (props) => (
  <button onClick={() => props.dispatch(login())}>ログイン</button>
);
```

実際には container コンポーネントでマッピングして利用することが多いです。
connect 関数の第 2 引数に、マッピングする関数を指定します。

```js
// Storeのどのデータをpropsにマッピングするか
const mapStateToProps = (state) => ({
  isLogin: state.Auth.isLogin,
});

// dispatchにどのActionCreatorをマッピングするか
const mapDispatchToProps = (dispatch) => ({
  login: bindActionCreators(login, dispatch),
  logout: bindActionCreators(logout, dispatch),
});

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

こうすると、コンポーネントからはマッピングされた関数を実行するのみです。

```jsx
const Button = (props) => (
  <button onClick={() => props.login()}>ログイン</button>
);
```

## Reducer

Action オブジェクトが dispatch 関数に渡されると、Reducer という関数が実行されます。
この Reducer が実行されると、Store のデータが更新されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/127338/907e2199-27d4-e24b-9edf-cb51ff36a034.png)

Reducer は現在の Store と Action を受け取り、次の Store を返す関数です。
Action の type によって、return する state が異なるのがわかるかと思います。

※スプレッド演算子（...state）を使っているのは、引数で渡された state をコピーするためです。
Redux は state の更新を検知するために、浅い比較（shallow equal）を行います。
state を直接変更すると更新を検知できず、コンポーネントが再レンダリングされなくなります。

```js
// 初期State
const initState = {
  isLogin: false;
};

// Reducer関数
function reducer(state = initState, action) {
  switch (action.type) {
    case LOGIN:
      return { ...state, isLogin: true };
    case LOGOUT:
      return { ...state, isLogin: false };
    default:
      return state;
  }
}
```

Reducer が実行され Store が変更されると、Store の情報はマッピングされたコンポーネントに渡されます。

props が更新されると、コンポーネントのツリーが自動的に更新されるので、Store が更新されると View が自動的に更新されるような挙動になります。

## Redux thunk

Store データの更新には dispatch 関数に Action オブジェクトを渡す、というのはわかったのですが、Action は type というキーが必須のただのオブジェクトです。
とてもシンプルですが、API 通信などの外部通信は、どこに記述するのが良いでしょうか？

```js
// Action Type
export const LOGIN = "Auth/LOGIN";

// Action Creator
export function login() {
  return { type: LOGIN };
}
```

通常 Action Creator は純粋な Action オブジェクトしか返しません。
Redux thunk を使うと、Action Creator が関数を返せるようになります。

```js
// Action Type
export const LOGIN = "Auth/LOGIN";

// Action Creator
export function login() {
  // dispatchを引数にとる関数を返す
  return async (dispatch) => {
    // API通信をawaitで待つ
    await fetch("/auth/login");
    // Actionをdispatch
    dispatch({ type: LOGIN });
  };
}
```

例えば API 通信の最中にアプリをローディング表示したい場合などは、以下のように通信前後で別々の Action を dispatch 関数に渡し、状態を変更します。

```js
// Action Type
export const FETCHING = 'Auth/FETCHING';
export const LOGIN = 'Auth/LOGIN';

// 初期State
const initState = {
  isFetching: false;
  isLogin: false;
};

// Reducer関数
function reducer(state = initState, action) {
  switch (action.type) {
    case FETCHING:
      return { ...state, isFetching: true };
    case LOGIN:
      return { ...state, isFetching: false, isLogin: true };
    case LOGOUT:
      return { ...state, isLogin: false };
    default:
      return state;
  }
}
// Action Creator
export function login() {
  // dispatchを引数にとる関数を返す
  return async (dispatch) => {
    // API通信前に別のActionをdispatch
    dispatch({ type: FETCHING });
    // API通信をawaitで待つ
    await fetch('/auth/login');
    // Actionをdispatch
    dispatch({ type: LOGIN });
  };
}
```

これでコンポーネント側で API 通信中の表示を制御できます。

```jsx
// ログインページのコンポーネント
const LoginPage = (props) => {
  // API通信中はローディングを表示
  if (props.isFetching) {
    return <Loading />;
  }
  return (
    <>
      <p>{props.isLogin ? "ログイン中" : "未ログイン"}</p>
      <Button />
    </>
  );
};

// ログインボタン
const Button = (props) => (
  <button onClick={() => props.login()}>ログイン</button>
);
```

# 最後に

以上、React + Redux アプリのデータの流れでした。
各ライブラリの挙動について、理解の助けになればと思います。