0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-06-07

はじめに

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

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

極めて単純な「押すと数字が増えるボタン」のコードを例として扱います。
counterButton.gif
全体の動きを追えるよう、本来はファイルを分割すべき所でも結合されたコードを貼っています。
動作確認はCodeSandboxでしています。

クラスコンポーネントの場合

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

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

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

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

クラスコンポーネント
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を使えるようになりました。

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

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

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. counterViewonClickに登録されたprops.incrementCountが呼ばれる
  2. mapDispatchToPropsincrementCountにより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を変動させるActionDispatcherを登録し、
引数propsとしてというのが基本の流れとなります。

使い分け

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

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

参考

(React公式) state とライフサイクル
(React公式) ステートフックの利用法

まとめ

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?