React学習 React学習ログ

【React学習ログ⑤】Reduxを実装しながら解説してみた

前回の記事でReduxの役割や全体図を解説しました。

関連【React学習ログ④】Reduxをどう理解したかを解説|図解あり

続きを見る

今回の記事では実際にReduxを使ってできるだけシンプルな内容とコードで実装し、解説もしていきたいと思います。

少しでも皆さんの理解につながると嬉しいです。

では解説を始めます。

カウンターの実装

今回の実装ではReduxを使ってカウンターを作っていきます。

こんな感じです。

コンポーネントの親子関係

全部で7つのSTEPがあります。

Reduxを使うための環境構築

まずはReduxをインストールします。

npm install redux

次にReduxをReactで使えるようにするためのパッケージをインストールします。(これを入れることでReact上でReduxのstoreやreducerなどが簡単に使える様になります。)

npm install react-redux

下準備はこれで完了です。

次から実際にコードを書いていきます。

Redux storeを作る

データセンターであるRedux store(storeとだけの表記の時もあります)を作ります。

その前にstoreを管理しやすくするために専用の「store」フォルダを作り、その中に「store.js」を作りそこにstoreを構築していきます。

import {createStore} from 'redux';

const store = createStore();

これでstoreの構築は完了です。

ここで問題があります。エディターによっては添付画像のようにcreateStoreに線が入りアラートが出てくる場合があります。

React Reduxでは現在createStoreではなくより便利な「Redux Toolkit」の使用を推奨しているためこのアラートが出てきてしまうようです。

createStoreでも問題なく動くので、Reduxを順を追って理解できるようになるため、まずはcreateStoreで実装をし、その後Redux Toolkitを使用しどの様に便利なのかを実感できるように解説します。

store内にReducer関数を設置する

データ更新機能であるReducer関数をstore内に設置します。

import { createStore } from 'redux';

const counterReducer = (state = { counter: 0 }, action) => {
  if (action.type === 'increment') {
    return {
      counter: state.counter + 1,
    };
  }
  if (action.type === 'decrement') {
    return {
      counter: state.counter - 1,
    };
  }
  return state;
};

const store = createStore(counterReducer);

Reducer関数はuseContextで詳しく解説しているので、ここではざっくりと説明します。

このReducer関数であるcounterReducer()はこの後設定するdispatchから受け取るactionオブジェクトを使い最新のstateを更新しています。

useContextと異なるのはReduxのReducer関数のstateに初期値を設定している点です。

useContextでは別のところに初期値を設定していましたね。

最終行のcreateStoreでReducer関数を呼び出しています。

これでRedux storeが構築される時(ページ訪問時やデータ更新時)にReducer関数が実行されます。
この時に先ほどの初期値を設定していないと、ページ読み込み時にstateの値がないと判断されエラーが出てしまいます。

参考【React Hooks基礎】useContextを理解する

続きを見る

Redux storeへの記述は一旦完了です。

このRedux storeを別のコンポーネントで使える様にエクスポートします。

import { createStore } from 'redux';

// 省略

const store = createStore(counterReducer);
export default store; //←追加

Providerの設置

Redux storeをReactアプケーションに供給するためにProviderを設定します。

やることは大きく2つです。

  1. AppコンポーネントをProviderで囲む
  2. ProviderとRedux storeを繋げる

基本的にコンポーネントの最上位にあるindex.jsに設定します。

こうすることでどのコンポーネントでもRedux storeにアクセスすることができます。

import React from 'react';
import ReactDOM from 'react-dom/client';
import {Provider} from 'react-redux';

import './index.css';
import App from './App';
import store from './store/store';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Provider store={store}><App /></Provider>);

3行目で一番最初にnpmでインストールした「react-redux」から「Provider」をインポートしています。

7行目で先ほど作ったRedux storeをインポートしています。

最終行で全体のAppコンポーネントをProviderで囲んでいます。

Providerには「store」と言うプロパティを持っており、そこに7行目にインポートしたstoreを読み込ませます。

Providerはデータ供給を可能にする環境を作ってくれるもので、 storeに供給したいデータを指定します。

こうすることでRedux storeをReactアプリケーション全体に供給することができるようになります。

この段階でアプリケーションとデータセンターが繋がったと言うことになります。

useSelectorでRedux storeのデータをコンポーネントで使う

Redux storeのデータを読み取るためにはuseSelectorと言うHookを使います。

Counter.jsに記述していきます。

注意点はReact Hookは今までreactからインポートしていましたが、useSelectorは「react-redux」からインポートします。

import { useSelector } from 'react-redux';

const Counter = () => {
  const counter = useSelector((state) => state.counter);

  return (
    <div>
      <div className="counter-btn">
        <div className="btn">
          +1
        </div>
        <div className="btn">
          -1
        </div>
      </div>
      <div className="btn">表示/非表示</div>
    </div>
  );
};

export default Counter;

useSelectorは関数を返します。

この関数はRedux store内のstateと繋がっています。
今回の実装ではRedux storeは最終的にcounterを返しているので、state.counterと記述します。

これを「counter」と言う変数に格納します。

const counter = useSelector((state) => state.counter);

ちなみにここでReducer関数に初期値を設定していないと、ページ読み込み時にCounterコンポーネントで記述した「counter」の値がない状態になるのでエラーが出ます。

これでRedux storeからデータを取得することができました。
現在は初期値である0が表示されています。

useDispatchでデータ更新をする

続いてボタンを押したらデータ更新ができるように実装していきます。

ここではReducer関数を動かすためのuseDispatch()の使い方を解説します。

引き続きCounter.jsに記述していきます。
Counter.jsには元々ボタンを実装しています。

まずはCounter.jsにuseDispatchを「react-redux」からインポートします。

import { useSelector, useDispatch } from 'react-redux';

const Counter = () => {
  const counter = useSelector((state) => state.counter)
  const dispatch = useDispatch();

  const incrementHandler = () => {
    dispatch({ type: 'increment' });
  };
  const decrementHandler = () => {
    dispatch({ type: 'decrement' });
  };
  return (
    <div>
      <div className="counter-btn">
        <div className="btn" onClick={incrementHandler}>
          +1
        </div>
        <div className="btn" onClick={decrementHandler}>
          -1
        </div>
      </div>
      <div className="btn">表示/非表示</div>
    </div>
  );
};

export default Counter;

5行目、useDispatchはReducer関数にactionオブジェクトを渡すdispatch関数を返します。

8行目と11行目でdispatch関数に{type: 〇〇}と言うオブジェクトを渡していますね。

これがactionオブジェクトです。

actionオブジェクトが作られるとすぐにReducer関数に渡され、Reducer関数が実行されます。
これはuseReducer()と同じですね。

関連【React Hooks基礎】useReducerを理解する

続きを見る

これでボタンを押すたびにdispatch関数が実行され、actionオブジェクトが生成されます。それがRedux storeのReducer関数へ渡されるようになり、値が変わるようになります。


Redux store内のReducer関数でaction.typeの条件分けによってデータ更新の内容が変わっていますね。

5増えるボタンを作る

押すと5増えるボタンを設置します。

結論として、actionオブジェクトに新しい値を追加すれば完成です。

import { useSelector,useDispatch } from 'react-redux';

const Counter = () => {
  const counter = useSelector((state) => state.counter)
  const dispatch = useDispatch();

  const incrementHandler = () => {
    dispatch({ type: 'increment' });
  };
  const decrementHandler = () => {
    dispatch({ type: 'decrement' });
  };
 //↓↓追加↓↓//
  const increaseHandler = () => {
    dispatch({ type: 'increase', amount: 5 });
  };
 //↑↑追加↑↑//
  return (
    <div>
      <div className="counter-btn">
        <div className="btn" onClick={incrementHandler}>
          +1
        </div>
        <div className="btn" onClick={increaseHandler}>
          +5
        </div>
        <div className="btn" onClick={decrementHandler}>
          -1
        </div>
      </div>
      <div className="btn">表示/非表示</div>
    </div>
  );
};

export default Counter;

上記のincreaseHandler内のdispatch関数の中のオブジェクトに新たなキーとバリューを追加しました。

Redux store内に条件わけを追加し、「action.amount」を追記して完成です。

import { createStore } from 'redux';

const counterReducer = (state = { counter: 0 }, action) => {
  if (action.type === 'increment') {
    return {
      counter: state.counter + 1,
    };
  }
  if (action.type === 'decrement') {
    return {
      counter: state.counter - 1,
    };
  }
//↓↓追加↓↓//
  if (action.type === 'increase') {
    return {
      counter: state.counter + action.amount,
    };
  }
//↑↑追加↑↑//
  return state;
};

const store = createStore(counterReducer);

export default store;

これでdispatch内のamountの数値を変えることで増える数値を任意に設定できるようになりました。

複数のstateの管理を同時に行う

最後にカウンターとは別に出力される数字の表示/非表示のstateの管理もしてみましょう。

まずはstore.jsを下記のように記述します。

import { createStore } from 'redux';

const counterReducer = (state = { counter: 0, showNum: true }, action) => {
  if (action.type === 'increment') {
    return {
      counter: state.counter + 1,
      showNum: state.showNum,
    };
  }
  if (action.type === 'decrement') {
    return {
      counter: state.counter - 1,
      showNum: state.showNum,
    };
  }
  if (action.type === 'increase') {
    return {
      counter: state.counter + action.amount,
      showNum: state.showNum,
    };
  }
  if (action.type === 'toggle') {
    return {
      counter: state.counter,
      showNum: !state.showNum,
    };
  }
  return state;
};

const store = createStore(counterReducer);

export default store;

追記した内容は、初期値にshowNum:trueを追加しました。

「showNmu:true」でカウンターの数字の表示、「showNmu:false」でカウンターの数字の非表示になるように実装していきます。

注意ポイントは条件分けを設定した各returnにもshowNumを追記しないといけない点です。

これはデータ更新時に新しいデータを上書きされるようになっているので、counterだけしか書かないと、初期値のcounterとshowNumの構成が変わってしまい、意図しない挙動になってしまいます。

複数のstateを管理したい場合には、初期値のプロパティー内容とreturnで返ってくるプロパティ内容は同じにしないといけないと覚えておきましょう。

続いてCounter.jsを変更します。

import { useSelector, useDispatch } from 'react-redux';

const Counter = () => {
  const counter = useSelector((state) => state.counter);
  const show = useSelector((state) => state.showNum);
  const dispatch = useDispatch();

  const incrementHandler = () => {
    dispatch({ type: 'increment' });
  };
  const decrementHandler = () => {
    dispatch({ type: 'decrement' });
  };
  const increaseHandler = () => {
    dispatch({ type: 'increase', amount: 5 });
  };
  const toggleHandler = () => {
    dispatch({ type: 'toggle' });
  };
  return (
    <div>
      {show && <h1>{counter}</h1>}
      <div className="counter-btn">
        <div className="btn" onClick={incrementHandler}>
          +1
        </div>
        <div className="btn" onClick={increaseHandler}>
          +5
        </div>
        <div className="btn" onClick={decrementHandler}>
          -1
        </div>
      </div>
      <div className="btn" onClick={toggleHandler}>
        表示/非表示
      </div>
    </div>
  );
};

export default Counter;

5行目でuseSelectorを使い新たに「showNum」のstateを取得しています。

そしてdispatchを設定しaction.typeを「toggle」になるように記述して完成です。

ここまでがReduxの基本的な実装になります。

長くなってきたので、ここでこの記事は終わりです。

次の記事で「Redux Toolkit」について書きます。

Redux Toolkitを使うことでより複雑な内容をシンプルに書くことができメンテナンスも楽になります。

最後に全体の流れをまとめた図をもう一度添付します。
Reduxはこの記事の内容を理解しないと攻略できないので理解できるまで何度も読みましょう。

最後に

このコンテンツでは一般のエンジニアがどのように言語(今回はフレームワークになりますが)を学習し、つまずき、乗り越えていっているのかを垣間見えるようにしていこうと思います。

できるだけ画像、動画などを盛り込んで視覚的に理解できるように工夫しています。

少しでもこれからプログラミングを学ぼうと考えている方の為になる発信をできれば思いますので、温かい目で見てもらえると嬉しいです。

今回私がReduxを解説する上で使った教材は、Udemyのコースになります。興味があればぜひ使ってみてください。

このコースの補足教材としてこのブログを使ってもらえると効果的だと思います。

ベストセラー取得

-React学習, React学習ログ