LoginSignup
2
3

More than 3 years have passed since last update.

React- Redux 入門

Posted at

Reduxの学習メモです

Fluxフローについて

まずはReduxにおけるFluxフローの全体像を理解してみます

なぜReduxを使うのか

  1. stateの見通しをよくするため
  2. どこからでもstateを参照/変更可能にするため
  3. モジュールを疎結合にするため
    =機能Aと機能Bがお互いに影響しあわない

ReactとReduxのstate管理の違い

Reactのみでstateを管理する場合

複数コンポーネントで利用しているstateに変更が発生した場合、
コンポーネントの親子関係を辿って変更を共有しないといけない。(stateのバケツリレー)
→親子関係が深く複雑になるほど管理が大変になる。

Reduxを使ってstateを管理する場合

複数コンポーネントで利用するstateをStoreに持たせることで、Storeに直接、変更をリクエストしたり、バケツリレーなしで関係するコンポーネントに変更を反映することができる。

Fluxフローとは

  1. データフロー設計の1つ
  2. データが常に1方向に流れる
  3. イベントによってデータが変化(イベント駆動)

Flux思想をReactの状態管理に適用したライブラリ
= Redux

Fluxフロー図

flux_flow.png

Actionsを書いてstateの変更を依頼する

Actionsの役割

アプリからStoreへデータを送るためのpayload(データの塊)を渡す役割
→アプリから受け取ったデータをReducersへ渡す

なぜActionsを使うのか

純粋にデータを渡す処理だけを記述するため
どのstateをどのように変更するのかについてはReducersに任せる

Actionsの書き方

ユーザーがサインインした時にStoreで管理しているユーザー情報の変更依頼を投げるAction

// Action typeを定義してexport
export const SIGN_IN = "SIGN_IN";

// Actionsはプレーンなobjectを返す
export const signInAction = (userState) => {
  // typeとpayloadを記述する
  return {
    type: "SIGN_IN",
    payload: {
      isSignedIn: true,
      uid: userState.uid,
      username: userState.username
    }
  }
};

Reducersの作り方とスプレッド構文の使い方

Reducersはstateの変更を管理する役割を持っている

Reducersの役割

Actionsからデータを受け取り
Storeのstateをどう変更するのかを決める
→Store内のstate(状態)の管理人

initialStateを作る

const initialState = {
  users: {
    isSignedIn: false,
    uid: "",
    username: ""
  }
};

export default initialState
  • Storeの初期状態(アプリケーション起動時)
  • アプリに必要なstateを全て記述
  • exportしておく

Reducersを作る

import * as Actionss from './actions';
import initialState from "../store/initialState";
  1. actionsファイル内のモジュールを全てimport(Actionsという名前をつける)
  2. initialStateをimport
export const UserReducer= (state = initialState.users, action) => {
    switch (action.type) {
        case Actions.SIGN_IN:
            return {
                ...state,
                ...action.payload
            };
        default:
            return state
    }
};

スプレッド構文について

スプレッド構文は配列やオブジェクトの要素を展開する
spread:広げる、開く、塗る

const payload = {
  uid: "1",
  username: "hoge"
}

console.log({...payload});
// {uid: "1", username: "hoge"}

// Merge Objects
const state = {
  isSignedIn: false,
  uid: "0",
  username: "fuga"
}
console.log({...state, ...payload});
// {isSignedIn: false, uid: "1", username: "hoge"}

Redux(Store)とReactを接続してstateを変更する

Storeはstateを保存する

  1. StoreとReducersを関連づける
  2. Redux(Store)とReactを接続する
  3. Storeの状態を変更する

Store | モジュールのimport

src/reducks/store/store.js
// reduxモジュールのimport
import {
    createStore as reduxCreateStore,
    combineReducers
} from 'redux';

// Reducersのimport
import { ProductsReducer } from '../products/reducers';
import { UsersReducer } from "../users/reducers";

export default function createStore() {
    // reduxのcreateStoreメソッドをreturn
    return reduxCreateStore(
        // combineReducersでstateを生成
        combineReducers({
            products: ProductsReducer,
            users: UsersReducer
        })
    );
}

combineReducers()とは?

  1. 分割したReducersをまとめる
  2. stateのカテゴリ毎
  3. オブジェクトをreturnする(stateのデータ構造)
combineReducers({
  products: ProductsReducer,
  users: UsersReducer
})

{
  products: {
    // productsのstate
  },
  users: {
    isSignedIn: false,
    uid: "",
    username: ""
  }
}

StoreとReactアプリの接続

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import createStore from "./reducks/store/store";
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

export const store = createStore();

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

serviceWorker.unregister();

react-reduxのProviderとは

  1. propsにstoreを渡す
    →ラップしたコンポーネントにstoreの情報を渡す
  2. Reactコンポーネント内でreact-reduxのconnect関数を使えるようにする
    →ReactとReduxを接続してstoreを変更できるように

ルーティングの設定

connected-react-routerを利用してルーティングする

ルーティング用ライブラリ

  1. react-router v4以降
    Reactのルーティング用ライブラリ
    Reduxとは関係なく、Reactで利用可能
  2. connected-react-router
    ReduxのStoreでルーティングを管理
    react-router v4 & v5と互換性あり

middlewareの導入

src/reducks/store/store.js
import {
    createStore as reduxCreateStore,
    combineReducers,
    applyMiddleware
} from "redux";
import { connectRouter, routerMiddleware } from "connected-react-router";
import { UsersReducer } from "../users/reducers";

export default function createStore(history) {
    return reduxCreateStore(
        combineReducers({
            router: connectRouter(history),
            users: UsersReducer
        }),
        applyMiddleware(
            routerMiddleware(history)
        )
    );
}

StoreとRouterの接続

src/index.js
import { ConnectedRouter } from "connected-react-router";
import * as History from 'history';

const history = History.createBrowserHistory();
export const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
      <ConnectedRouter history={history}>
          <App />
      </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);
serviceWorker.unregister();

Routerコンポーネントを作る

src/Router.jsx
import React from "react";
import { Route, Switch } from "react-router";
import { Login, Home } from "./templates";


const Router = () => {
    return (
        <Switch>
            <Route exact path="/login" component={Login} />
            <Route exact path="(/)?" component={Home} />
        </Switch>
    );
};

export default Router;
  1. Routeコンポーネントでパスとコンポーネントを指定する
  2. exactは「パスとぴったり一致したら」という意味。
    Switchコンポーネントと一緒に使う。

(/)? スラがあってもなくてもという意味

Routerコンポーネントを使う

src/App.jsx
import React from "react";
import Router from "./Router";

const App = () => {
    return (
        <main>
            <Router />
        </main>
    );
};

export default App;

templatesファイルの作成

src/templates/Login.jsx
import React from "react";
import { useDispatch } from "react-redux";
import { push } from "connected-react-router";

const Login = () => {
    const dispatch = useDispatch();
    return (
        <div>
            <h2>ログイン</h2>
            <button onClick={() => dispatch(push('/'))}>
                ログイン
            </button>
        </div>
    );
};

export default Login;
src/templates/Home.jsx
import React from "react";

const Home = () => {
    return (
        <h2>Home</h2>
    );
};

export default Home;

re-ducksパターンでファイルを管理する

ディレクトリ構成のベストプラクティス

re-ducksパターンとは

  1. Reduxのディレクトリ構成
  2. ファイルを管理しやすくする
  3. Ducksパターンから派生

Ducksパターン以前

actions
├ products.js
└ users.js

reducers
├ products.js
└ users.js

問題点:

ファイル名が同じなので、

actionについて書いてるんだっけ?reducerだっけ?となる

Ducksパターン

modules
├ products.js
└ users.js

reducerはactionを参照するんだから一つにまとめちゃえとなったのが
Ducksパターン

re-ducksパターン
users
├ ctions.js
├ index.js
├ operations.js
├ reducers.js
├ selectors.js
└ types.js

なぜre-ducksパターン?

Ducksパターンの課題を解決するため
(ファイルの肥大化)

modules/users.js
export const UsersReducer= (state = initialState.users, action) => {
    ...
};

export const SIGN_IN = "SIGN_IN";
export const signInAction = (userState) => {
    ...
};

export const SIGN_OUT = "SIGN_OUT";
export const signOutAction = () => {
    ...
};

re-ducksパターンのメリット

  1. actionsとreducersがシンプルになる
  2. ファイルが肥大化しにくくなる
  3. ファイル毎の役割が明確で管理しやすい
products
├ actions.js
├ index.js
├ operations.js
├ reducers.js
├ selectors.js
└ types.js

users
├ actions.js
├ index.js
├ operations.js
├ reducers.js
├ selectors.js
└ types.js

各ファイルの役割

operations

  1. 複雑な処理を任せられる
  2. redux-thunkで非同期処理を制御する
  3. Actionsを呼び出す

types

  1. TypeScriptで使う
  2. 型定義を記述してexport

selectors

  1. Storeで管理しているstateを参照する関数
  2. reselectというnpmモジュールを使う
src/reducks/users/selectors.js
import { createSelector } from "reselect";

const usersSelector = (state) => state.users;

export const getUserId = createSelector(
  [usersSelector],
  state => state.uid
);

redux-thunk

Redux内の非同期処理を制御する

redux-thunkとは

Reduxで非同期処理を制御するライブラリ

通常のActionsはaction objectを受け取る

= 関数を受け取ることができない

= async/awaitやPromiseを使えない

redux-thunkの導入方法

src/reducks/store/store.js
import thunk from "redux-thunk";

export default function createStore(history) {
    return reduxCreateStore(
        combineReducers({
            router: connectRouter(history),
            users: UsersReducer
        }),
        applyMiddleware(
            routerMiddleware(history),
            thunk
        )
    );
}
  1. モジュールimport
  2. applayMiddleware()に追加

redux-thunkの基礎文法

src/reducks/users/operations.js
export const signIn = (email, password) => {
    return async (dispatch, getState) => {
        const state = getState();
        const isSinedIn = state.users.isSignedIn;

        if (!isSinedIn) {
            const userData = await emailSignIn(email, password);
            dispatch(signInAction({
                isSinedIn: true,
                uid: '00001',
                username: 'hoge'
            }))
        }
    };
};

コンテナの役割

Storeと接続されたコンテナコンポーネントを作る

コンテナコンポーネントはStoreとコンポーネントの中継役

コンテナコンポーネントの役割

Reduxの世界とReactの世界を繋ぐ

  1. stateをフィルタリングしてコンポーネントに渡す
  2. Storeからdispatchする関数=Actionsをコンポーネントに渡す

いつ使う?

コンテナーコンポーネントを使用するのに必要な手続きが面倒
→現在はRedux Hooksを使う方法がオススメ

明示的にstateをフィルタリングしたいとき

connect()の使い方

src/reducks/containers/Login.js
import LoginClass from '../templates/LoginClass';
import {compose} from 'redux';
import {connect} from 'react-redux';
import * as Actions from '../reducks/users/operations';

const mapStateToProps = state => {
  return {
    users: state.users // 渡したいstateだけをオブジェクト型で記述
  }
}
src/reducks/containers/Login.js
const mapDispatchToProps = dispatch => {
  return {
    actions: {
      signIn() {
        dispatch(Actions.signIn()) // StoreからDispatchする関数
      }
    }
  }
}

export default compose(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )
)(LoginClass);
src/reducks/templates/LoginClass.jsx
import React, {Component} from 'react';

export default class LoginClass extends Component {
  render() {
    return (
      <div>
        <h2>ログイン</h2>
        <button onClick={() => this.props.actions.signIn()}>
          ログインする
        </button>
      </div>
    )
  }
}

Hooksでの代替方法

mapStateToProps → useSelector()
mapDispatchToProps → useDispatch()

2
3
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
2
3