はじめに
Reactでコンポーネント内の情報を保持するには、stateを使う必要があります。
クラスコンポーネント、Hooks、Reduxどれを利用するかで手順が異なるため、
簡潔にそれぞれの書き方や利点についてまとめました。
極めて単純な「押すと数字が増えるボタン」のコードを例として扱います。
全体の動きを追えるよう、本来はファイルを分割すべき所でも結合されたコードを貼っています。
動作確認はCodeSandboxでしています。
クラスコンポーネントの場合
React16.8(2019/2)まで関数コンポーネントではstateが使えず、
React.Componentを継承したクラスコンポーネントを使う必要がありました。
この書き方にはthis
、bind
、constructor
等、
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を使えるようになりました。
クラスコンポーネントに比べ、副作用を抑えつつ、非常に簡潔に書けます。
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単体と比べコード量は多くなりがちですが、
情報の受け渡しが単純になるため、大規模アプリにおいて有用です。
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()
で加算しています。
解説
ユーザーがボタンを押した時の動作の流れをコード上から追跡すると、
-
counterView
のonClick
に登録されたprops.incrementCount
が呼ばれる -
mapDispatchToProps
のincrementCount
によりdispatch
が呼ばれる -
dipatch
によってcountReducer
が呼ばれる -
countReducer
の引数にはStoreにある現時点のstateと、Actionである{type:INCREMENT_ACTION_TYPE}
が渡される -
countReducer
によって変更後のstateが返され、Storeに格納される -
mapStateToProps
に書かれた通りStoreにあるstateがpropsとしてcounterView
に渡される
Storeにアクセスするコンポーネントでは、
react-reduxの関数connect(mapStateToProps,mapDispatchToProps)(component)
によって、
mapStateToProps
に参照するstate、
mapDispatchToProps
にstateを変動させるActionのDispatcherを登録し、
引数propsとしてというのが基本の流れとなります。
使い分け
基本的に関数コンポーネントを使い、Hooksでstateを管理し、
情報のやり取りが複雑化してきたらReduxを導入すればよいというのが私の考えです。
関数コンポーネントでどうしても実装できない処理に対する最終手段としてなら、
クラスコンポーネントを使うのもアリだと思います。
参考
(React公式) state とライフサイクル
(React公式) ステートフックの利用法
まとめ
記事を投稿するのはこれが初めてなため、
改善点等ありましたら、是非教えてほしいです!