Reduxについて


こんにちは!最近、Reduxに関して学習したので、その備忘録としてQiitaにまとめてみようと思います!
もし何処か間違いなどあれば、よろしければご指摘お願いいたします!

また、解説するコードの一部は、僕が学習に使用した教材を参考に自身で作成したアプリケーションの内容となっています。(以下のGithubリポジトリ内のコードを参照していただくと少しは記事の内容を追えるかと思います
アプリのGithubリポジトリ:https://github.com/TaikiYamano/React-Tweet-With-Redux

Reduxとは

ReduxとはReactなどdのプリでStateを管理するフレームワークのこと。
Reactでアプリを作成する際、Reduxを使わない場合は、React自身のコンポーネント内でStateを管理していましたが、Reduxを使うとStateを別の場所で管理でき、どのコンポーネントからでもStateにアクセスできるようになります。Stateの管理を別の場所で行うことにより、ReactのコンポーネントはStateの管理をする必要がなくなります。
ReduxはReact以外の言語とも併せて使用することも可能らしい(もちろん単体でも)ですが、Reactとの相性が一番いいらしいです。

Reduxの5つの登場事項

Reduxで登場する5つのキーワードは以下の5つです。
・Action
・ActionCreator
・State
・Store
・Reducer

Action

Store(後に説明)で管理されているStateを更新する為に必要なもの。実態はJavascriptのオブジェクトです。typeプロパティを必要とします。(それ以外は特に必須ではありません。)
以下は、「投稿者名」、「本文」、「画像」を投稿できるアプリ(Twitterのようなもの)で1件の投稿(ツイート)を作成して、投稿する場合を想定したActionです。

1、必須のtypeプロパティの値には「何をするか」についての情報をセットします。(今回の場合は投稿(ツイート)を作成&投稿なのでCREATE_POSTとしました。)typeにセットされる値はstring型の定数が一般的だそうです。

2〜4、投稿(ツイート)の情報です。今回は「名前(name)」「本文(text)」「image(画像)」を投稿(ツイート)の情報として定義しました。これらの値は必須ではありません。

{
 ①type: CREATE_POST,
 ②name: "山田太郎", //投稿者名
 ③text: "こんにちは!", //本文
 ④image: "hogehuga.png" //画像
}

この情報をStoreに送り、Stateを更新します。

ActionCreator

その名の通り、Actionを作るもの。個人的にはActionを発行する製造工場みたいなイメージを持っていますが、実態は単にオブジェクト(Action)を返すJavascriptの関数です。
以下は、上記で説明した、投稿(ツイート)を作成し、投稿するActionを作成する、ActionCreatorです。

const createPost = (name,text,image) => {
    return {
        type: CREATE_POST,
        name: name
        text: text
        image: image
    };
};

今回は、CREATE_POSTというtypeを持つ、Actionを作成するActionCreatorなので、関数名はcreatPostとしました。引数に投稿(ツイート)の情報である「名前(name)」「本文(text)」「image(画像)」を受け取ります。そして、引数から受け取った情報をオブジェクトのプロパティにセットして、関数の返り値とします。

State

アプリの状態のこと。「投稿者名」、「本文」、「画像」を持った投稿(ツイート)を投稿でき、ユーザーが投稿(ツイート)に「いいね」を押した場合は、「いいね」を押した投稿(ツイート)のみを閲覧できるアプリを想定すると、Stateは以下のようになります。
※以下の投稿(ツイート)情報の投稿ID(id)といいねフラグ(liked)は投稿を作成した際に自動的に定義されるものとしています。アクションで定義したプロパティではありません。

{
①投稿されたツイートの情報
  posts: [
    {
      id: 1,
      name: '山田太郎',
      text: 'こんにちは!',
      image: 'hoge.jpg',
      liked: true
    },
    {
      id: 2,
      name: '田中花子',
      text: 'おはよう!',
      image: 'huga.png',
      _liked: true
    }
  ],
②どの投稿(ツイート)の一覧を表示させるか(全ての投稿、もしくはいいねを付けた投稿のみ)
  postFilter: 'ALL_POSTS'
}

投稿されたツイートの情報(posts)と、どのツイートの一覧を表示させるか(postFilter)をStateとして保持しています。

Store

上記で説明した、Stateを保持する場所のこと。アプリケーション内で1つしか存在しません。すなわちアプリケーションの状態(State)は1箇所で一括管理されます。また、StoreはStateを保持する以外にも、
・getStateメソッドでStateにアクセスする
・dispatchメソッドでStateを更新する(引数にActionを受け取る)
・subscribeメソッドでStateの更新を検知する(引数にコールバック関数、listenerを受け取る)
という役割を持ちます。

Reducer

StateとActionを渡すと、Actionの内容に応じて新しいStateを返してくれるもの。個人的にはActionと古いStateを渡すと、新しいStateと交換してくれる交換所のようなイメージを持っているのですが、実態はJavascriptの関数で、StateとActionを引数に引き取り、Actionのtypeプロパティの値を参照し、その値に応じて新しいStateを返します。(引数で受け取ったStateを更新するのではなく、新しいStateを返します。)
以下は、投稿(ツイート)を投稿する、削除する、投稿にいいねをつけることができるアプリを作成した場合のReducerであるpostReducerを定義した場合です。引数にState(投稿を格納する配列)とActionを受け取っています。

※今回のアプリケーションではモデルを用いて投稿(post)を作成しています。モデルから作られたオブジェクトは初期値に
・id(投稿ID)
・name(投稿者名)
・text(本文)
・image(画像)
・liked(いいね→初期値はfalse)
を持っています。

また、投稿の「いいね」の値を取得する為のhasLikedメソッドと初期値がfalseである「いいね」をtrueに変更する為のlikeToggleメソッドを持っています。

export const postReducer = (state = [], action) => {
    switch(action.type){
        ①case CREATE_POST:
            const post = new Post(action.name,action.text,action.image);
            return [
                ...state,
                post
            ];

        ②case DELETE_POST:
            return state.filter((post, index) => {
                return action.id !== post.id; 
            }); 

        ③case LIKE_POST_TOGGLE:
            return state.map((post,index) => {
                if(action.id !== post.id){
                    return post;
                }
                const likedPost = new Post(post.name,post.text,post.image);
                if(!post.hasLiked()){
                    likedPost.likeToggle();
                }
                return likedPost;
            });

        ④default:
            return state;
    }
};

1、ActionがCREATE_POST、つまり新規で投稿(ツイート)を投稿する際の処理です。Stateに新たに作成した投稿(post)を追加するのですが、先程説明した通り、ReducerはStateを更新してはいけないので、スプレッド演算子でStateのコピーを作り、その中に新規作成された投稿を格納してStateを返しています。

2、ActionがDELETE_POST、つまり投稿を削除する際の処理です。Stateに対してfilterメソッドを使い、削除対象の投稿を除いた新しいStateを返しています。

3、ActionがLIKE_POST_TOGGLE、つまり投稿にいいねをつける際の処理です。Stateに対してmapメソッドを使い、いいねを付けた対象の投稿のいいねフラグをtrueにした上で新しく作成された配列を返します。

ActionがLike_POST_TOGGLEであった場合の処理に関してもう少し詳しく解説すると、mapメソッドで繰り返し処理が行われる投稿(post)の中で、いいねを付ける対象ではない投稿(post)の場合は、そのまま何もせずに値を返します。一方、いいねを付ける対象の投稿(post)で、いいねフラグがfalseであった場合はlikeToggleメソッドでtrueにし、反対に既にいいねフラグがtrueであった場合はいいねを付ける対象の投稿(post)の内容をそのままコピーして新規作成した投稿(likedPost)を返すようにしています。(Postモデルから作られたオブジェクトのいいねフラグの初期値はfalseである為)

これで、いいねの対象になる投稿のみのいいねフラグがtrueになった新しいStateが返されます。

4、Actionが上記のどれでもない場合はStateがそのまま返されます。

Storeの作成方法

上記で説明したStoreはReduxが提供しているcreateStoreメソッドを使うことで作成することができます。
引数にはReducerを受け取ります。

以下は先程説明した、postReducerを用いてStoreを作成した場合です。

//ReduxからcreateStoreメソッドのインポート
import { createStore } from 'redux';
//postReducerメソッドのインポート
import  postReducer  from '../reducers/postReducer';

//Storeの作成
const store = createStore(postReducer);

export default store;

これでStoreを作成することができます。

Reducerが複数ある場合のStore作成方法

上記ではReducerが1つの場合のStoreの作成方法を説明しましたが、Reducerが2つ以上あった場合、それぞれのReducerに対してStoreを作ることはできません。(Storeはアプリ内で1つしか存在できない為)
このような場合はReduxが提供するcombineReducersメソッドを使って、複数のReducersを1つにまとめてcreateStoreメソッドの引数に渡します。

ReduxからcombineReducersメソッドのインポート
import { combineReducers } from 'redux';
//1つ目Reducerのインポート
import { postReducer } from '../reducers/postReducer';
//2つ目のReducerのインポート
import { postFilterReducer } from '../reducers/postFilterReducer';

const postAppReducers = combineReducers({
    posts: postReducer, //1つ目のReducer
    postFilter: postFilterReducer //2つ目のReducer
});

export default postAppReducers;

上記のようにcombineReducersメソッドの引数にオブジェクトを渡し、任意のキー名の値にReducerをセットします。こうすることで、複数のReducerを1つにまとめることができます。

import { createStore } from 'redux';
import postAppReducers from '../reducers/index';

const store = createStore(postAppReducers);

export default store;

そして先程と同様、createStoreの引数にcombineReducersメソッドで1つにまとめられたReducer(postAppReducers)を渡すと、2つのReducerの内容を持ったStoreを作成することができます。

参考記事

Reduxの学習をするにあたり、使用した教材の他に以下の記事を参考にさせて頂きました!

Redux入門【ダイジェスト版】10分で理解するReduxの基礎
Redux 入門 〜Reduxの基礎を理解する〜
React + Redux の基本的な使い方