LoginSignup
6
6

More than 5 years have passed since last update.

Reactと対比しながらReduxを理解する

Last updated at Posted at 2018-12-09

なるべくPureなReactと対比して書いているので、分かりやすくなってる(といいな。。)
なおハンズオンではありませんので悪しからず。

ツッコミどころはたくさんあるかもしれませんが、絞った内容とパターンを抑えて説明していきます。

まず何ができるの?

Reduxを導入することで何ができるのか、、、

image.png

まぁお馴染みですが、Propsのバケツリレーがなくなりますね。

実際に自プロジェクトでのReduxを導入したことによって得られたメリットをあげると、次のGifの様になります。

導入前
タブを切り替える際に毎回データのフェッチ、ローディングが走る
6h26r-xpv9q.gif

導入後
データをStoreで管理し無駄なデータのフェッチがなくなりローディングが毎回走らなくなった

tm7jw-o1dqj.gif

Gifだとわかりにくいですが、導入前は下部のTabを切り替えるときにAPIへのfetch処理が毎回走ってしまうことでローディングが走っておりUXが悪くなってしまいます。
親のコンポーネントでデータを保持すればもちろんいけますが、複雑性が増したり、何よりナンセンスなのでReduxを導入しました。
これによってよりネイティブアプリに近い挙動を再現できています。

Reduxの概要

image.png

まずReduxに入ると、Fluxが〜とか単一方向に〜とか聞きますが、その辺りは今回は説明しません。
自分は物覚えも悪くそこから入っても最初「?」しか出なかった為です。

下記のReactプロジェクトでのコードを見ながら役割を比較して説明していきます。
なおReduxの細かいコード内容についてはまた今度描きます。

sample.js
class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      data: [],
    }
    this.fetchData = this.fetchData.bind(this);
  }

  fetchData() {
    axios.get('/fetchData').then((res) => {
      this.setState({
        data: res.data.data,
      });
    })
  }

  render() {
    return (
      <button
        onClick={this.fetchData}
      >
        Fetch Data
      </button>
    );
  }
}

Store

StoreはStateを一括して保存してくれている貯め場の様な場所だと思ってください。
これは1つのプロジェクトにつき1つしか作りません。
上記のコード内には対応する部分はありません。

Reducer

Reducerは初期Stateの宣言、および後述するActionから渡ってきた指令によってSwitch分で分岐しながらStateを変更してくれる存在です。
ReactプロジェクトだとsetState()をする感覚と同じです。

image.png

Action

Actionはその名の通り関数を定義するファイルになります。
Reactでの違いと比べるとこんな感じで、データを取得してきたり、Stateを変更するためのDataの準備をする行動 = Actionといった感覚になります。
公式には「Actionを発行する」とか言いますが、いきなり言われても意味わかりませんよね。。。

image.png

Reactにconnect

ここまで出てきているStoreReduceractionはReduxが持っているものです。
Reactにとっては全く無関係な状態です。
なのでReactとReduxを繋ぐ為にreact-reduxというライブラリを使います。
下記の実装で見ていきます。

実装 & 雛形

少し説明する箇所が多くなりますが、ざっくりと説明していきます。
想定としては、よくあるページのロード → APIからデータの取得 → データおよびビューの更新です。

1. storeの定義

( 1 ) - createStore()Reduxのstoreを使うよ!的な宣言をします。中のcombineReducersはreducerをまとめたオブジェクトをcombineReducersを使うことによって2つ以上のReducerをまとめてくれています。
applyMiddleware(thunk)の部分は、まずapplyMiddlewareはRedux用のサードパーティー製のライブラリを使う際、この中で定義してあげます。有名どころだとredux-devtoolsなど入れることが多いです。
そしてその中で定義しているthunkですが、これは本来Reduxでは非同期処理に対応していない為、非同期処理を可能にしてくれるライブラリになります。
非同期処理用のパッケージには

などがあり、各々メリット、デメリットがあります。
詳細は割愛しますが、今回は一番使いやすいredux-thunkを使用します。

( 2 ) - 上記で作成したstoreProvider内で呼び出します。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import App from './User/pages/App';
import reducers from './reducers/';

const store = createStore(combineReducers(reducers), applyMiddleware(thunk)); // (1)

ReactDOM.render(
  <Provider store={store}> // (2)
    <BrowserRouter>
      <Route path="/" component={App} />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root'),
);

2. reducerでinitialStateの定義

初期ステートを定義します。
上記で説明したReactでいうthis.state={}の部分のようなものだと思っていてください。
最初はdefault argumentを与え、随時初期ステートを上書いて更新していく形になります。

reducers/post.js
const initState = {
  data: [],
};

export default (state = initState, action) => {
  const { type, payload } = action;
  switch (type) {
    default:
      return state;
  }
}

なお先ほどIndexでcombineReducersの話がありましたが、2つ以上のReducerを定義する際には以下のようにオブジェクト型にラップしてエクスポートします。

reducers/index.js
import post from './post';
import sample from './sample';

export default {
  post,
  sample,
};

3. Componentでの呼び出し

ここがReduxとReactの繋がる大きなポイントになります。
( 1 ) - まずReactの際はただコンポーネントをエクスポートするだけでしたが、ReactとReduxを繋げる際はconnect関数を使います。慣れてない方だと見慣れない構文ですがconnect()(<Component />)とし括弧の2つ目にコンポーネントを記載します。
これはHOCs(ハイオーダーコンポーネント)、高階関数といいます。今回は割愛します。

(2) - そして括弧の1つ目のmapStateToPropsですが、ここではStoreにある値を全て参照できます。Reducerで定義して、Storeに保存されている値を全て参照できるので、この中から必要なReducerの値だけを取ってきます。
* mapDispatchToPropsを定義するやり方もありますが、今回は定義しません。

(3) - connectしてきた値はPropsで受け取ることができます。

Post.js
import React from 'react';
import { connect } from 'react-redux';

class Sample extends React.Component {
  render() {
    return (
      <div>
        {this.props.data.map(data => data.name)} // (3)
      </div>
    );
  }
}

const mapStateToProps = ({ post }) => post; // (2)

export default connect(mapStateToProps)(Sample) // (1)

4. actionの定義

概念編で見た通り関数を定義します。
その際dispatchを受け取ってdispatch内でtypepayload定義します。
typeにはReducerで分岐させるためのTypeを渡します。
payloadには実際のデータを入れます。(実際にはpayloadと言う名前でなくても良いが、payloadが推奨されている)

actions/post.js

import axios from 'axios';

export const FETCH_DATA = 'FETCH_DATA'; // (1)

export const fetchData = () => (dispatch) => {
  axios.get('/fetchData').then((res) => {
    dispatch({
      type: FETCH_DATA,
      payload: res.data.data,
    });
  });
};

5. reducerにてstateの更新

先ほどactionにてエクスポートしたtypeをインポートし、caseで分岐します。
その際にstateをpayloadのデータで上書きます。

reducers/post.js
+ import { FETCH_DATA } from '../actions/post';

const initState = {
  data: [],
};

export default (state = initState, action) => {
  const { type, payload } = action;
  switch (type) {
+    case FETCH_DATA:
+      return {
+        ...state,
+        data: payload
+      };
    default:
      return state;
  }
}

6. Componentにてactionsの呼び出し

あとは、actionで定義した関数を呼んであげるだけです。
今回のやり方だと、connectするとdispatchのPropsが渡ってきますので、dispatchでラップした上で関数を呼んであげましょう。

Post.js

import React from 'react';
import { connect } from 'react-redux';
+ import { fetchData } from '../actions/post';

class Sample extends React.Component {
+  componentDidMount() {
+    this.props.dispatch(fetchData());
+  }
  render() {
    return (
      <div>
        {this.props.data.map(data => data.name)}
      </div>
    );
  }
}

const mapStateToProps = ({ post }) => post;

export default connect(mapStateToProps)(Sample)

まとめ

今回の内容は本に載ってる内容だったりとは少し違う書き方ですが、個人的には一番書きやすいと思った書き方です。

Reduxは複雑すぎると言う声が多いですが、自分なりの雛形を覚えてそこから発展させていけばそんなに難しく感じずに進められるのかなと思いました。(自分はかなり苦しんだ。。。。)

PureなReactがContext APIだったり、今回は関係ないけどHooksやSuspenceだったり充実してきているので、なん年後かには使われなくなっているんですかね。。。

まぁ、それにしてもReduxは書いてて楽しいです

6
6
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
6
6