[React] Reduxで非同期処理してデータを表示

フロントエンド界隈でJavaScriptフレームワークやそれらに関連するアーキテクチャのお話が活発に行われている昨今、自分も触る機会が出てきているので記事タイトルのことについて備忘録がてらブログを書いてみたいと思います。

今回は少し前に経験した”APIサーバを叩いて返ってきたJSON形式のデータを表示させた”ときの例をもとに紹介したいと思います。実装にはReact + Reduxを使ってみます。

実際のデモはこちら

実際のソースコードはこちらにあげています。
yusukehigasa/ReactReduxAsyncExample

主な機能は以下です。

  • ユーザー情報が返ってくるAPIサーバを読みに行く
  • 返ってきたユーザー情報を表組みで表示する
  • 5秒おきにAPIサーバを読みに行く

ファイル(ディレクトリ)構成

Reduxを扱うときに考えさせられる点になりますが今回は下記の構成にしています。

Action(src/js/actions)

APIサーバを読みに行く関数を定義しています。サーバ通信には後述するaxiosを利用し返ってきた結果をtypeプロパティで判別payloadプロパティで値を保持しています。

Reducer(src/js/reducers)

現在のstateとActionを受け取りActionに応じて変更した結果を返す関数を定義しています。Reduxでは複数のreducerをインポートしたReduxのcombineReducersを使って1つのreducerにまとめStoreへ渡す考えが一般的なようです。

Store(src/js/store)

stateを管理する役割を担っていてインポートしたReduxのcreateStoreを使い関数として定義したReducerを渡しStoreを作成します。このときActionとReducerで非同期処理が行えるようredux-thunkというミドルウェアを噛ませる必要があるようです。ついでにredux-loggerというミドルウェアも使ってstateがどう変化したか・どのActionが実行されたかをログに出力しています。

Components(src/js/components)

コンポーネント化したビューにStoreが管理する状態遷移を流し込んでいます。このStoreとReactのコンポーネントを繋ぐためconnectメソッドを使い第一引数にmapStateToProps(stateから利用する値を取り出してpropsに設定)を第二引数にmapDispatchToProps(変更を伝えるAction、今回の場合はAPIサーバを読みに行くアクションを作成)を渡しています。

サーバ通信

React+Reduxで非同期処理をするときにいくつかのライブラリがあるようですが今回はaxiosを利用しています。特徴は非同期の結果をコールバックではなくプロミスで返してくれる点のようです。この辺は「Promiseでコールバック地獄から解放された話」の記事がとても良い解説をしてくれているので読むとより理解が深まるかと思われます。今回はaxiosのGETメソッドを使って非同期処理が成功したときと失敗したときでそれぞれのアクションを呼び出しています。

// src/js/actions/userActions.js

import axios from "axios";

const client = axios.create({
    xsrfHeaderName: "X-CSRF-Token",
    withCredentials: true
});

export function fetchUsers() {
    return function(dispatch) {
        client.get("api/user.json")
            .then((response) => {
                dispatch({
                    type: "FETCH_USERS_FULFILLED",
                    payload: response.data
                });
            })
            .catch((err) => {
                dispatch({
                    type: "FETCH_USERS_REJECTED",
                    payload: err
                });
            })
    }
}

サーバ通信の呼び出し

コンポーネント側でaxiosで非同期処理をしているメソッドを呼び出し成功したときと失敗したときにそれぞれのビューを展開しているだけです。今回は5秒おきにAPIサーバを読みに行くよう設定しています。

// src/js/components/Main.js

constructor(props) {
    super(props);
    this.timeout = null;
}

componentWillReceiveProps(nextProps) {
    if(!nextProps.fetching) {
        clearTimeout(this.timeout);
        this.startPoll();
    }
}
componentDidMount() {
    this.props.fetchUsers();
}
componentWillUnmount() {
    clearTimeout(this.timeout);
}
startPoll() {
    this.timeout = setTimeout(() => this.props.fetchUsers(), 5000);
}

render() {
    if (this.props.error != null) {
        return <p>Error x(</p>
    }

    if (!this.props.fetched) {
        return <p>Loading...</p>
    }

    return (
        <div>
            <UserList users={this.props.users} />
        </div>
    )
}

axiosのPOSTメソッドを使ってサーバへデータを投げる処理もGETメソッドの応用になるのでまた機会があったら別途記事を書きたいと思います。

とくに参考にさせてもらった文献

  • 2017.11.09
  • お仕事っぽいこと