【React】 ミニマルなコードで見るstate - クラス、Hooks、Reduxそれぞれでの使い方と利点

# はじめに

Reactでコンポーネント内の情報を保持するには、stateを使う必要があります。

クラスコンポーネント、Hooks、Reduxどれを利用するかで手順が異なるため、
簡潔にそれぞれの書き方や利点についてまとめました。

極めて単純な「押すと数字が増えるボタン」のコードを例として扱います。
![counterButton.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1339062/a19ef0bd-35cd-16d1-3b4a-dd6474861290.gif)
全体の動きを追えるよう、本来はファイルを分割すべき所でも結合されたコードを貼っています。
動作確認は[CodeSandbox](https://codesandbox.io)でしています。


# クラスコンポーネントの場合
React16.8(2019/2)まで関数コンポーネントではstateが使えず、
React.Componentを継承したクラスコンポーネントを使う必要がありました。

この書き方には`this`、`bind`、`constructor` 等、
Hooksを使う場合と比べ冗長な部分が多く、現在ではあまり使われていないようです。

しかしながら、Hooksではクラスコンポーネントの動作を完全には再現できなかったり、
Reactについて調べていると、クラスを使っている文献がまだまだ多かったりするため、
クラスコンポーネントを使うメリットもあります。

関数コンポーネントで実装するのが難しい処理の作成を急ぐ場合等に、こちらを使えばよいと思います。

```javascript:クラスコンポーネント
import React from "react";
import ReactDOM from "react-dom";

class CounterComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.incrementCount = this.incrementCount.bind(this);
  }

  incrementCount() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return <button onClick={this.incrementCount}>{this.state.count}</button>;
  }
}

ReactDOM.render(
  <CounterComponent />,
  document.getElementById("root")
);
```

### stateの宣言から書き換えまで

- `this.state = { count: 0 }`でstateの宣言・初期化を行い、
- `this.state.count`で参照、
- `this.setState({ count: this.state.count + 1})`で書き換え。


この超単純なコンポーネントを実装するのに、**8回もの`this`を書く必要**があります。
また、`onClick`に渡す関数を`bind(this)`する必要があるなど面倒です。

# Hooks(関数コンポーネント)の場合
現在主流の書き方です。
Hooksとは、関数コンポーネントの機能を拡張するため実装されたReactの機能のことです。
この中の`useState`により関数内でstateを使えるようになりました。

クラスコンポーネントに比べ、副作用を抑えつつ、非常に簡潔に書けます。

```javascript:Hooks
import { useState } from "react";
import ReactDOM from "react-dom";

function CounterComponent(props) {
  const [count, setCount] = useState(0);

  const incrementCount = () => setCount(count + 1);

  return <button onClick={incrementCount}>{count}</button>;
}

ReactDOM.render(
  <CounterComponent />,
  document.getElementById("root")
);
```

### stateの宣言から書き換えまで

- `const [count, setCount] = useState(0)`でstateの宣言をしています。

分割代入により`count`という変数にstateの参照、`setCount`にstateを変更する関数を代入しています。

- `count`で参照できています。
- `setCount(count + 1)`で書き換えることができます。

# Reduxの場合
React単体では、stateが各コンポーネントに分散しており、
該当のstateを持たないコンポーネントにpropsで情報を注ぎ込む必要があります。

そこで問題になってくるのが遠い親戚コンポーネント間での情報のやり取りです。
例えば、ひ孫コンポーネントに情報を渡す必要がある場合、
stateを持った親から子へ、子から孫へ、孫からひ孫へpropsを渡す必要がありました。

Reduxでは一つしか存在できない**Store**と呼ばれる場所ですべてのstateを管理し、
結びつけたコンポーネントから直接アクセスできるため、propsのバケツリレーから解放されます。

**Flux**の思想に従い、**View**、**Action**、**Reducer**等のパーツを作る必要があります。
そのため、単純なアプリケーションでは、React単体と比べコード量は多くなりがちですが、
情報の受け渡しが単純になるため、大規模アプリにおいて有用です。

```javascript:Redux
import ReactDOM from "react-dom";

import { createStore } from "redux";
import { Provider, connect } from "react-redux";

const INCREMENT_ACTION_TYPE = "INCREMENT";

const countReducer = (state = 0, action) => {
  switch (action.type) {
    case INCREMENT_ACTION_TYPE:
      return state + 1;
    default:
      return state;
  }
};

const mapStateToProps = (state) => ({
  count: state
});

const mapDispatchToProps = (dispatch) => ({
  incrementCount: () => dispatch({ type: INCREMENT_ACTION_TYPE })
});

const counterView = (props) => (
  <button onClick={props.incrementCount}>{props.count}</button>
);

const CounterComponent = connect(
  mapStateToProps,
  mapDispatchToProps
)(counterView);

const store = createStore(countReducer);

ReactDOM.render(
  <Provider store={store}>
    <CounterComponent />
  </Provider>,
  document.getElementById("root")
);
```
### stateの宣言から書き換えまで
- countReducerの引数初期化式`state = 0`で初期値を代入しています。

今回はstateに直接数値を入れていますが、
`state = { count:0 }`等、オブジェクトを渡す例が多いです。

- mapStateToPropsで指定した`count`を使い、`props.count`で参照をします。
- mapDispatchToPropsで指定した`incrementCount`を使い、`props.increment()`で加算しています。

### 解説
ユーザーがボタンを押した時の動作の流れをコード上から追跡すると、

1. `counterView`の`onClick`に登録された`props.incrementCount`が呼ばれる
2. `mapDispatchToProps`の`incrementCount`により`dispatch`が呼ばれる
3. `dipatch`によって`countReducer`が呼ばれる
4. `countReducer`の引数には**Store**にある現時点のstateと、**Action**である`{type:INCREMENT_ACTION_TYPE}`が渡される
5. `countReducer`によって変更後のstateが返され、**Store**に格納される
6. `mapStateToProps`に書かれた通り**Store**にあるstateがpropsとして`counterView`に渡される

**Store**にアクセスするコンポーネントでは、
react-reduxの関数`connect(mapStateToProps,mapDispatchToProps)(component)`によって、
`mapStateToProps`に参照するstate、
`mapDispatchToProps`にstateを変動させる**Action**の**Dispatcher**を登録し、
引数propsとしてというのが基本の流れとなります。

# 使い分け
基本的に関数コンポーネントを使い、Hooksでstateを管理し、
情報のやり取りが複雑化してきたらReduxを導入すればよいというのが私の考えです。

関数コンポーネントでどうしても実装できない処理に対する最終手段としてなら、
クラスコンポーネントを使うのもアリだと思います。

# 参考
[(React公式) state とライフサイクル](https://ja.reactjs.org/docs/state-and-lifecycle.html)
[(React公式) ステートフックの利用法](https://ja.reactjs.org/docs/hooks-state.html)

# まとめ

記事を投稿するのはこれが初めてなため、
改善点等ありましたら、是非教えてほしいです！