Reac初心者でも読みば必ずわかるReactのRedux講座

Reduxを利用することですべてのコンポーネントからアクセス可能なデータを一箇所で一元管理することができます。
アクセス可能なデータを一元管理??Reactの初心者の方であればデータを一元管理するということがどういうことを行っているのかも理解し難いかもしれません。Reactではコンポーネント間でデータを渡す場合に親コンポーネントから子コンポーネントへpropsを使って行います。親子関係のないコンポーネント間ではデータを渡すことができません。また親の子のみの関係であればpropsを一度渡すだけでいいのですが子コンポーネントにさらに子コンポーネントがあるとpropsをバケツリレーのように渡していなかければなりません。アクセス可能なデータを一元管理する場所があればそのような問題を解消することができます。そのためのライブラリがReduxです。どのコンポーネントからも同じ方法で共有したデータにアクセスすることが可能です。propsでは親から渡されたデータを子コンポーネントで更新することはできませんがReduxでは更新することも可能です。

Reduxは状態管理ライブラリ(State Management Library)と呼ばれています。状態管理という言葉ではどのような機能を持っているのかイメージすることが難しいので最初は”すべてのコンポーネントで1箇所に保存されているデータを共有できる仕組み”と考えた方がわかりやすいと思います。
本文書ではReduxで共有したデータへのアクセス方法と変更方法を学んでいきます。まずはRedux上でデータの共有化の設定を行い、データへのアクセス方法(Reduxで共有しているデータをブラウザ上で表示)を確認します。データへのアクセス方法を確認後にデータ変更方法を説明していきます。
環境の構築
Reduxの説明を行う前に今回利用するReactプロジェクトを作成します。任意の名前のReactプロジェクトを作成してください。ここではredux-learnにしています。
% npx create-react-app redux-learn
作成したプロジェクトディレクトリに移動して、npmコマンドを実行してReactが起動するか確認を行ってください。
% cd redux-learn
% npm start

インストール直後のフォルダ構成は下記の通りです。

本文書で利用しないファイル(logo.svg, App.test.js etc..)を削除しindex.jsファイルを更新します。


index.cssファイルは下記のように更新します。
* {
margin: 0;
padding: 0;
}
App.cssの中身はすべて削除し、App.jsを更新します。
import React from "react";
import "./App.css";
function App() {
return (
<div>
<h1>Redux Learn</h1>
</div>
);
}
export default App;
ブラウザで確認するとApp.jsに記述したRedux Learnがブラウザの左上に表示されます。

これでReduxの動作確認するための環境構築は完了です。
Reduxデータへのアクセス方法
Reduxは名前からもReact専用のライブラリのように思うかもしれませんが、React専用ではなく他のフレームワークでも利用することが可能です。そのためReactでReduxを利用するためにはReduxのコアであるreduxライブラリとReactのコンポーネントからReduxにアクセスするためのreact-reduxのライブラリの2つを利用します。
Reduxの設定を初めて行う人であれば共有化したデータにアクセスするだけなのにこんなにわけのわからない手順が必要なのと思うはずです。また機能のイメージと関連するのが難しい単語がいくつも出てくるためなぜそんな設定が必要なのかという疑問も出てきます。最初はあまり考えすぎずReduxで共有化したデータにアクセスできるところまで我慢して読み進めてください。
Reduxのインストール
ReactでReduxを利用するためにはreactとreact-reduxをインストールする必要があります。npmコマンドでインストールを行ってください。
% npm install redux react-redux
データ保管場所Storeの作成
Reduxではアプリケーション全体で共有するデータを保管する場所が必要になります。その場所はstoreと呼ばれアプリケーションを構成するコンポーネントとは全く独立した場所を作成します。
プロジェクトディレクトリにstoreディレクトリを作成しindex.jsファイルを作成してください。index.jsファイルの中でstoreの作成を行います。
import { createStore } from "redux";
const store = createStore();
export default store;
Reduxでは上記のようにcreateStoreという関数でstoreを作成します。
この後に説明を行っていきますが、createStoreの引数にはreducerという関数が必須です。reducerはコンポーネント間で共有を行いたいデータを保持し、reducer関数の中で唯一共有したデータを変更することができます。reducerという名前からは何を行うものなのか全くイメージが湧きませんがReduxで重要な役割を持ちます。
Reducerの作成
storeを作成するためにはreducerが必須となります。reducerは現在のデータの状態を保持するstateを持ちます。stateには初期値が必要となりreducer関数を実行すると必ずstateを戻します。
const initialState = {
count: 1,
};
const reducer = (state = initialState) => {
return state;
};

作成したreducerをcreateStoreの引数として設定します。これでreducerが保持しているstateをstoreの中に保管することができました。
import { createStore } from "redux";
const initialState = {
count: 1,
};
const reducer = (state = initialState) => {
return state;
};
const store = createStore(reducer);
export default store;
ここまでの設定ではインストールしたreduxのライブラリのみ利用してstoreにcountというデータを保存することができました次はReactのコンポーネントからReduxのデータにアクセスする必要があるのでreact-reduxライブラリを使い設定を行っていきます。
Providerコンポーネント
ReactのコンポーネントからReduxのstoreにアクセスするためにはProviderコンポーネントが必要となります。index.jsファイルのルートコンポーネント<APP />をProviderコンポーネントで包みます。さらに作成したstoreをimportしてstoreをpropsとしてProviderコンポーネントに渡します。
Providerでコンポーネントを包むことでreduxという独立した機能をReactと連携させているとイメージを持ってもらえるかと思います。
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux"
import store from "./store/index";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);
Providerコンポーネントは、ReactのコンポーネントからReduxのデータにアクセスするために必要な要素ですが、Providerだけではアクセスすることはできません。Reduxで共有化したデータにアクセスするためにconnect関数またはuseSelector Hookを利用します。

storeのcountへのアクセス
connet関数やuserSelectorを利用しなくてもstoreのデータにアクセスすることは可能なので簡単に説明しておきます。
storeをimportしてstore.getState().countを実行することでstoreに保存されているcountにアクセスすることもできます。
import "./App.css";
import store from './store/index'
function App() {
return (
<div className="App">
<h1>Redux Learn</h1>
<p>Count:{store.getState().count}</p>
</div>
);
}
export default App;

connect関数の利用
connect関数を利用してAppコンポーネント(App.js)からstoreに保管されているcountへのアクセスを行います。
あるコンポーネントからReduxのstoreにアクセスするためにconnectを利用します。connectには引数としてmapStateToProps関数を指定します。mapStateToProps関数ではstoreの中で設定したstateをAppコンポーネントにデータを渡せるpropsへと変換(map)しています。
mapStateToProps関数の中ではstateのどの値をpropsとしてコンポーネントに渡すのか中の処理で設定することができます。下記ではstateの設定したcountをcountという名前のpropsでAppコンポーネントに渡しています。
const mapStateToProps = state => {
return { count: state.count }
}

作成したmapStateToPropsの関数をconnectの引数に設定し以下のように記述する必要があります。mapStateToPropsはconnect関数からstoreのstateを渡されます。
export default connect(mapStateToProps)(App);
ここまでの設定を行ってようやくstoreに保管されているcountにコンポーネントからアクセスすることが可能となります。mapStatePropsで戻しているcountがAppのコンポーネントのpropsとして渡されていることも確認することができます。
import React from "react";
import "./App.css";
import { connect } from "react-redux";
function App({ count }) {
return (
<div className="App">
<h1>Redux Learn</h1>
<p>Count: {count}</p>
</div>:
);
}
const mapStateToProps = (state) => {
return { count: state.count };
};
export default connect(mapStateToProps)(App);
ブラウザから確認するとReduxのstoreで設定したcountが表示されます。

store¥index.jsファイルのinitialStateの値を1から100に変更するとブラウザで表示される値も100となります。Storeに保管したデータに間違いなくコンポーネントからアクセスしていることがわかります。
const initialState = {
count: 100,
};

他のコンポーネントからのアクセス
AppコンポーネントからReduxのStoreのデータにアクセスすることができました。これだけではすべてのコンポーネントでデータを共有している?という疑問が湧くと思いますので他のコンポーネントからも同様の方法でアクセスできるのか確認をしておきます。
srcディレクトリにcomponentsを作成し、countコンポーネントを作成します。Storeのデータのアクセス方法についてはAppコンポーネントで実行した方法と同じです。
import React from "react";
import { connect } from "react-redux";
function Count({ count }) {
return (
<>
<div>Countコンポーネント:{count}</div>
</>
);
}
const mapStateToProps = (state) => {
return { count: state.count };
};
export default connect(mapStateToProps)(Count);
作成したCountコンポーネントをAppコンポーネントでimportします。
import React from "react";
import "./App.css";
import { connect } from "react-redux";
import Count from "./components/Count";
function App({ count }) {
return (
<div className="App">
<h1>Redux Learn</h1>
<p>Count: {count}</p>
<Count />
</div>
);
}
const mapStateToProps = (state) => {
return { count: state.count };
};
export default connect(mapStateToProps)(App);
ブラウザで確認するとCountコンポーネントからもAppコンポーネントと同様の方法でアクセスできることが確認できます。APPコンポーネントでアクセスしたcountとCountコンポーネントでアクセスしたcountが表示されます。

useSelector Hooksを利用
ここまではReduxのデフォルトの機能(mapStateToProps, connect)を利用してReduxのデータにアクセスを行ってきましたが新たにReact Hooksが登場し、redux-reactのuseSelector Hooksを利用することができます。useSelectorを利用するとmapStateToPropsとconnect関数をuseSelectorに置き換えることができるのでコードがすっきりします。connect関数ではpropsでstateの値を渡していましたがuseSelectorではpropsを利用しません。
CounterコンポーネントのみでuseSelectorを設定します。
import React from "react";
import { useSelector } from "react-redux";
function Count() {
const count = useSelector((state) => state.count);
return (
<>
<div>Countコンポーネント:{count}</div>
</>
);
}
export default Count;
結果は先ほどと変わりません。useSelectorを利用することでReduxのデータへのアクセス方法も簡単になったことが実感できると思います。

AppコンポーネントもuseSelectorで書き換えると下記のように表示されます。
import "./App.css";
import { useSelector } from "react-redux";
function App() {
const count = useSelector((state) => state.count);
return (
<div className="App">
<h1>Redux Learn</h1>
<p>Count: {count}</p>
</div>
);
}
export default App;
配列データの確認
countだけではシンプルすぎるのでReduxのstoreに配列データを保管した場合はどのような方法でブラウザに表示させるか確認を行っておきます。
store/index.jsファイルに配列postsを追加し、初期値を設定します。これでstoreの中にはcountとpostsが保管されることになります。どちらもコンポーネントからアクセスすることが可能です。
import { createStore } from "redux";
const initialState = {
count: 50,
posts: [
{ id: 1, title: "Reduxについて" },
{
id: 2,
title: "ReduxのHooksについて",
},
],
};
const reducer = (state = initialState) => {
return state;
};
const store = createStore(reducer);
export default store;
Counterコンポーネントで追加したpostsデータをuseSelector Hookを利用して取得して展開します。postsのデータが取得できれば後はmap関数で展開します。
import React from "react";
import { useSelector } from "react-redux";
function Count() {
const count = useSelector((state) => state.count);
const posts = useSelector((state) => state.posts);
return (
<>
<div>Countコンポーネント:{count}</div>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</>
);
}
export default Count;
ブラウザ上では下記のように表示されます。Reduxで共有しているデータへのアクセス方法がわかれば後は通常通りの方法で表示させればいいことがわかりまます。

useSelectorではなくconnect関数とmapStateToPropsを利用した場合は下記のように記述することができます。
import "./App.css";
import { connect } from "react-redux";
function App({count,posts}) {
return (
<>
<div>Countコンポーネント:{count}</div>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</>
);
}
const mapStateToProps = (state) => {
return {
count: state.count,
posts: state.posts
};
};
export default connect(mapStateToProps)(App);
先ほどmapStateToPropsの説明時に”stateのどの値をpropsとしてコンポーネントに渡すのか中の処理で設定することができます。”と言いましたが上記ではcount, postsをpropsとしてCountコンポーネントに渡していますがCountコンポーネントではpostsが必要でないのであればmapStateToPropsの中でpostsを戻す処理は必要ありません。
Reduxデータの変更方法
ここまでの説明でReduxデータへのアクセス方法を理解することができました。次はReduxで共有化したデータの変更方法について確認していきます。
データを変更するためにはreducerのさらなる理解と新たにACTIONとdispatchの理解とそれらがどのような役割を持っているかを理解する必要があります。
Reducerについて
Reduxのデータへのアクセスに注目していたためreducer関数の引数にはstateのみ指定し初期値を与えてそのまま初期値を持ったstateを戻すというものでした。ただコンポーネントで共有化したデータを見るだけであればこのままの設定で問題はありませんが、アプリケーションを構築するためには必ず共有化したデータの変更が必要となります。
Reducerの本来の目的は現在の状態であるstateとACTIONを受け取り、ACTIONで指示された内容でstateに変更を加え、新たな状態を作るというものです。
reducer関数の中でのみstoreに保存されたデータの変更を行うことができるためstoreのデータを変更したい場合はreducerを介して行わなければなりません。
storeの中のデータにどのような変更を行いたいかをACTIONを使って指示を出しreducer関数がその指示を受け取って、reducer関数の中で変更の処理を行います。
ReduxのACTIONについて
ACTIONSとはtypeプロパティを持っているJavaScriptのオブジェクトです。
{
type: 'INCREASE_COUNT'
}

この形式をもったACTIONをreducerが受け取ってStoreのデータに対して変更を加えます。ACTIONはオブジェクトなのでACTION自体が何かの処理を行うことはありません。
ReducerとACTIONについて
Reducerはstateとactionを引数にとることができます。関数の中ではactionの内容によりstateに変更を加えて変更が加わった新しいstateを戻します。
const reducer = (state = initialState, action) => {
//actionによりstateに変更を加えて変更が加わった新しいstateを戻す
return state
}
変更する内容はACTIONのtypeによって異なるのでswitchによって分けることができます。action.typeをチェックすることでtypeによって異なる処理を行うことができます。先ほどまでのcountの例を使ってcountを1増やす処理と1減らす処理をreducerを使って行いたい場合は下記ように記述することができます。
action.typeがINCREASE_COUNTの場合はstateのcountの値を1増やし、action.typeがDECREASE_COUNTの場合はstateのcountの値を1減らします。何もaction.typeの指定がない場合はそのままstateを戻すというものです。
const reducer = (state = initialState, action) => {
switch (action.type) {
case "INCREASE_COUNT":
return {
count: state.count + 1,
};
case "DECREASE_COUNT":
return {
count: state.count - 1,
};
default:
return state;
}
};
ACTIONのtypeによりreducerでどのような処理を行うのが決まると最後はどのようにACTIONをreducerに伝えるか確認が必要となります。伝えるための方法がdispatch関数です。
DispatchとACTION
dispatch関数の引数にはACTIONを指定することができます。ACTIONはtypeを持つオブジェクトなので下記のように記述することができます。
dispatch({ type: "INCREASE_COUNT" });
dispatch関数をメソッド内で実行することができればreducerに実行したいACTIONが伝わり、storeの中のデータを変更することができます。
dispatchを実行させるためにAppコンポーネント内にクリックイベントを追加します。Upボタンをクリックするとincreaseメソッドが実行され、Downボタンを押すとdecreaseメソッドが実行されます。
<div className="App">
<h1>Redux Learn</h1>
<p>Count: {count}</p>
<button onClick={increase}>Up</button>
<button onClick={decrease}>Down</button>
</div>
increase, decreaseメソッドの中でdispatch関数を実行します。コンポーネントのpropsにdispatchが出てきましたこれがどこからきているのか気になるところです。connectの第2引数に何も指定していない場合にconnect関数はコンポーネントにpropsでdispatch関数(props.dispatch)を渡します。
import "./App.css";
import { connect } from "react-redux";
function App({ dispatch, count }) {
const increase = () => {
dispatch({ type: "INCREASE_COUNT" });
};
const decrease = () => {
dispatch({ type: "DECREASE_COUNT" });
};
return (
<div className="App">
<h1>Redux Learn</h1>
<p>Count: {count}</p>
<button onClick={increase}>Up</button>
<button onClick={decreate}>Down</button>
</div>
);
}
const mapStateToProps = (state) => {
return {
count: state.count,
posts: state.posts
};
};
export default connect(mapStateToProps)(App);
ブラウザで確認するとUpとDownのボタンが表示されます。

UpボタンをクリックするとCount数が増え、DownボタンをクリックするとCount数が減ることを確認してください。ここまでの設定でStoreのデータの変更方法を理解することができました。
mapDispatchToPropsの利用
reducerにACTIONを伝えるためにdispatch関数を利用しましたがACTIONは他の方法でもreducerに伝えることができます。ここではmapDispatchToPropsとconnectを利用します。mapDispatchToPropsに変更してもブラウザ上動作は変わりません。mapDispatchToPropsを利用することでmapDispatchToPropsの中でclickイベントのメソッドを定義し、propsを利用して定義したメソッドをコンポーネントに渡します。mapDispatchToPropsを利用しない場合はclickイベントのメソッドはコンポーネントの中で定義していました。
import React from "react";
import "./App.css";
import { connect } from "react-redux";
function App({ count, increase, decrease }) {
return (
<div className="App">
<h1>Redux Learn</h1>
<p>Count: {count}</p>
<button onClick={increase}>Up</button>
<button onClick={decrease}>Down</button>
</div>
);
}
const mapStateToProps = (state) => {
return { count: state.count };
};
const mapDispatchToProps = (dispatch) => {
return {
increase: () => dispatch({ type: "INCREASE_COUNT" }),
decrease: () => dispatch({ type: "DECREASE_COUNT" }),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);

useDispatch Hooksの利用
Storeのデータにアクセスした時にuseSelector Hooksが利用できたようにデータの変更に関するHooksもあります。変更についてはuseDispatch Hookを利用します。useDispatch Hookで利用するためにはreact-reduxからimportする必要があります。
useSelectorを利用したCountコンポーネントでuseDispatchの設定を行います。connectを利用する必要がなくuseSelectorとuseDispatchを利用するとコードがすっきりします。
import React from "react";
import { useSelector, useDispatch } from "react-redux";
function Count() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
const increase = () => {
dispatch({ type: "INCREASE_COUNT" });
};
const decrease = () => {
dispatch({ type: "DECREASE_COUNT" });
};
return (
<>
<div>Countコンポーネント:{count}</div>
<button onClick={increase}>Up</button>
<button onClick={decrease}>Down</button>
</>
);
}
export default Count;
ここまで読み進めてStore, ACTION, Reducer, Dispatchが何でどのような役割をするのか答えることができればReduxの理解はできているかと思います。理解することと利用することは別のものなのでいろいろなネット上に落ちている例を使って理解を深めてください。下記のTodoリストのアプリケーションの作成も理解を深めるのに役に立ちます。