CodeZine(コードジン)

特集ページ一覧

アプリの状態管理を安全に行うためのFluxとRedux

基礎からはじめるReact入門 第8回

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2018/04/09 14:00
目次

ReduxでFluxなアプリを実装する(2)

カウンターアプリのRedux部分を実装する

 それでは、カウンターアプリの状態管理をReduxで実装していきましょう。Redux特有の部分として、次のファイルを作成します。

  • src/actions/counter.js
  • src/reducers/index.js
  • src/reducers/counter.js
図5 Reduxのために追加したファイル
図5 Reduxのために追加したファイル

 それぞれの実装内容について説明していきます。まずはActionを作ります。src/actions/counter.jsを作成しましょう。

リスト3 src/actions/counter.js
// amountの数だけカウントを増やすためのActionを作成する
export function increment(amount) {
  return {
    type: 'INCREMENT', // (1)
    payload: { // (2)
      amount
    }
  };
}

// amountの数だけカウントを減らすためのActionを作成する
export function decrement(amount) {
  return {
    type: 'DECREMENT',
    payload: {
      amount
    }
  };
}

 これらはActionを表すオブジェクトを返す関数で、Action Creatorと呼ばれます。Actionはただのオブジェクトであればよく、(1)の通りtypeプロパティを持っていることだけが必須の条件です。type以外は何を書いてもよいのですが、慣例として(2)の通りpayloadプロパティの中にパラメータを置いています。この関数によって作られたActionオブジェクトをdispatch関数に渡すことで、データフローは動き出すのです。

[コラム]Flux Standard Action

 Actionのデータ構造はかなり緩いものですが、あまりに自由すぎるルールは混乱を招きます。普通にデータを渡したり、発生したエラーについて通知したりするといった、ありふれたユースケースくらいは、標準化されていてほしいものです。そんなモチベーションから有志によって策定された規格が「Flux Standard Action」です。

 Flux Standard ActionにのっとってActionを作る場合は、通常の規定とは別に、次のルールを守りましょう。

  • データを渡す場合にはpayloadプロパティにオブジェクトを定義して渡すこと
  • エラーを表すActionである場合にはerrorプロパティをtrueにし、payloadにエラーオブジェクトを渡すこと
  • もし付加情報をpayloadとは別に通知したい場合にはmetaプロパティに渡すこと

 これにのっとると、通常のデータを渡すActionはリスト4のように定義することになります。

リスト4 データを渡すAction
{
  type: 'ADD_TODO',
  payload: {
    text: 'Do something.'
  }
}

 また、エラーが発生した場合にはリスト5の通り定義することになります。多くの場合、これはtry-catch構文のcatch句の中でdispatchされ、catch句からもらったエラーオブジェクトをpayloadに渡すことになります。

リスト5 エラーの発生を伝えるAction
{
  type: 'ERROR_IN_ADD_TODO',
  payload: new Error("has error!"),
  error: true
}

 今回は詳しく紹介できませんが、Flux Standard ActionにのっとったActionやReducerを簡単に定義するためのライブラリとしてredux-actionsといったライブラリもRedux向けに提供されています。Reduxに慣れてきた頃に、導入を検討してみるといいでしょう。

 次に、このActionを受け取るReducerの実装を見ていきましょう。まずはsrc/reducers/counter.jsを作成します。

リスト6 src/reducers/counter.js
const defaultState = { // (5)
  count: 0
};

const counter = (/* (1) */state = defaultState, /* (2) */action) => {
  switch(action.type) {
    case 'INCREMENT':
      return { // (3)
        count: state.count + action.payload.amount
      };
    case 'DECREMENT':
      return {
        count: state.count - action.payload.amount
      };
    default:
      return state; // (4)
  }
};

export default counter;

 Reducerはひとつのオブジェクト(state)をactionに応じて変換し、新しいオブジェクトに作り直して返却する関数です。(1)が変更前の現在の状態で、(2)が前述のActionをdispatchした結果として送り込まれてきたものです。Actionのtypeに応じて処理を振り分け、最終的には(3)の通り変更後の状態を新しいオブジェクトとして返します。ReducerにはあらゆるActionが渡されるので、関係ないActionが届いたときには(4)の通りstateをそのまま返し、何も起こらないようにしておくことも重要です。また、起動した時点では「現在の状態」というものが存在しないので、(5)のデフォルト引数で初期状態を定義しておくとよいでしょう。

 こうして作られた、counterという名前のReducerが管理するstateは、次のデータ構造になります。

リスト7 counterが管理するデータ構造の例
{
  count: 0
}

 さて、Reducerひとつで管理できる状態オブジェクトはひとつだけですが、複数のReducerを使ってさまざまな状態を管理することができます。そのための記述をsrc/reducers/index.jsに行います。

リスト8 src/reducers/index.js
import { combineReducers } from 'redux'
import counter from './counter';

const rootReducer = combineReducers({
  counter: counter // (1)
});

export default rootReducer;

 (1)の通り、combineReducersに渡すオブジェクトにReducerを登録していくことで、複数のReducerを取りまとめて管理することができます。こうしてできたrootReducerのデータ構造は次の通りになります。

リスト9 rootReducerが管理するデータ構造
{
  counter: {
    count: 0
  },
  // リスト8の(1)でReducerを増やすと、ここにデータが追加されていく
}

 リスト6のような関数もReducerですし、それらを束ねてリスト8のように加工したものもReducerです。少し混乱しそうになりますが、そういうものだと理解してください。

 ここまでで、Redux特有のコードが作成できました。

カウンターアプリのReact部分を実装する

 それでは、ReduxのコードをReactにつなぎこんでいきましょう。まずはエントリーポイントであるindex.jsの変更です。

リスト10 src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import rootReducer from './reducers';
import App from './App';

// reducerを元にstoreを生成する
const store = createStore(rootReducer); // (1)

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

 Reducerは、(1)でstoreに取り込まれて、状態管理に使われます。(2)では、ReactコンポーネントであるProviderにstoreを渡すことで、App以下のすべてのReactコンポーネントがstore内のデータを受け取れるようになります。

 これで、AppコンポーネントにReduxのデータが渡るお膳立てができました。あとはsrc/App.jsを変更するのみです。App.jsは次のようになります。

リスト11 src/App.js
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { increment, decrement } from './actions/counter';

class App extends Component {
  /** +1ボタンを押した際にactionを発行する処理 */
  _handlePlusOneClick(e) {
    this.props.dispatch(increment(1)); // (4)
  }
  /** -1ボタンを押した際にactionを発行する処理 */
  _handleMinusOneClick(e) {
    this.props.dispatch(decrement(1));
  }
  render() {
    const count = this.props.count; // (3)
    return (
      <div>
        <button onClick={e => this._handleMinusOneClick(e)}>-1</button> 
        <span>{count}</span> 
        <button onClick={e => this._handlePlusOneClick(e)}>+1</button> 
      </div>
    );
  }
}
/** store内のデータをpropsに変換する */
function mapStateToProps(state) { // (2)
  return {
    count: state.counter.count
  };
}
export default connect(mapStateToProps)(App); // (1)

 Appコンポーネントへの変更で最も重要なのは、(1)のconnect関数です。ReduxとReactコンポーネントを「つなげる」役割を果たしてくれます。大別すると、次のふたつの機能があります。

  • Reducerによって分割管理されたstate(store内の状態データ)をReactのpropsに加える
  • dispatchをReactのpropsに加える

 まず前者ですが、(2)のmapStateToPropsで定義した関数によって、Reducerごとに保存された状態をコンポーネントのpropsに変換することができます。これによって、(3)の通りコンポーネントの中でReducer由来の値を使えるようになります。(2)の引数に渡されるstateは、リスト9で紹介した、rootReducerが管理しているデータ構造です。

 そして後者として、コンポーネント内でpropsからdispatch関数がもらえるようになります。これにより、(4)の通りActionを発行できるようになります。increment(1)の戻り値はリスト6で実装した通り、typeを持つオブジェクト=Actionです。これをdispatchに渡すことで、Actionが発行されます。

 これでカウンターアプリが動くようになりました。

まとめ

 今回はFluxとReduxの基礎的な部分を紹介しました。今回作ったActionのパラメータを変えれば「+5」ボタンのといったものも簡単に作れますし、新たに「リセット」ボタンを作ってみるのも面白いかと思います。配布しているサンプルコードにはそういった機能も追加しているので、興味がある方は見てください。

 次回は、Reduxを実践的な状況でも扱えるようにするために、非同期処理を題材にして深い使い方を学んでいきます。



  • LINEで送る
  • このエントリーをはてなブックマークに追加

バックナンバー

連載:基礎からはじめるReact入門

もっと読む

著者プロフィール

  • WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

    <WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂...

  • 山田 祥寛(ヤマダ ヨシヒロ)

    静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for ASP/ASP.NET。執筆コミュニティ「WINGSプロジェクト」代表。 主な著書に「入門シリーズ(サーバサイドAjax/XM...

あなたにオススメ

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5