Redux で同期、非同期 API 通信を行う [React.js]


Redux で API を使って外部と通信をするのに適切なレイヤーは Action Creator が良いとされています。

👽 Action Creator について

Action Creator とは、Action を作るためのロジックを記載する部分です。

// Action creator
function setUserName(userName) {
return {
type: "SET_USER_NAME",
UserName
};
}

Action Creator はシンプルな Action オブジェクトを返す想定になっており、同期処理が前提となります。

🐯 Action Creator での非同期処理

Action Creator で非同期処理を行うためには、以下の middleware を使うのが一般的なようです。

😎 Sample Code for Redux Thunk

Reddit API を Redux Thunk を使って呼び出すサンプルコードが、Redux の公式ドキュメントに公開されています。
大変わかりやすいサンプル実装ですので、ぜひ読んでサンプルコードを実装してみてください。

Example: Reddit API - Redux

🍣 Middleware を使わない実装

redux-thunkredux-saga は学習コストが高いため、それらを使わずに実装するというアイデアもあります。

https://qiita.com/uryyyyyyy/items/d8bae6a7fca1c4732696

A component

サンプルとなるコンポーネントです。

import React, { Component } from "react";
import { ActionClass } from "./Actions";

type Props = {
someState: SomeState,
actions: ActionClass
};

export class Counter extends Component<void, Props, void> {
render() {
const loading = this.props.value.loadingCount === 0 ? null : <p>loading</p>;
return (
<div>
{loading}
<p>{`score: ${this.props.value.num}`}</p>
<button onClick={() => this.props.actions.increment(3)}>
Increment 3
</button>
<button onClick={() => this.props.actions.asyncIncrement()}>
async Increment 100
</button>
</div>
);
}
}

ActionClass

ActionCreator をまとめたクラスです。

export const INCREMENT = "counter/increment";
export const FETCH_REQUEST_START = "counter/fetch_request_start";
export const FETCH_REQUEST_FINISH = "counter/fetch_request_finish";

export class ActionClass {
dispatch: (action: any) => any;

constructor(dispatch: (action: any) => any) {
this.dispatch = dispatch;
}

increment(amount: number) {
this.dispatch({ type: INCREMENT, amount: amount });
}

async asyncIncrement(): Promise<void> {
this.dispatch({ type: FETCH_REQUEST_START });

try {
const response: Response = await fetch("/api/count", {
method: "GET",
headers: headers,
credentials: "include"
});

if (response.status === 200) {
const json: JsonObject = await response.json();
this.dispatch({ type: INCREMENT, amount: json.amount });
} else {
throw new Error(`Illegal status code: ${response.status}`);
}
} catch (err) {
console.error(err.message);
} finally {
this.dispatch({ type: FETCH_REQUEST_FINISH });
}
}
}

Container

ActionClass と Component をつなぎこむ Container の実装サンプルです。

import * as React from "react";
import { connect } from "react-redux";
import type { Dispatch } from "redux";
import { Counter } from "./Counter";
import { ActionClass } from "./Actions";

const mapStateToProps = (state: any) => {
return { someState: state.someState };
};

const mapDispatchToProps = (dispatch: Dispatch<any>) => {
return { actions: new ActionClass(dispatch) };
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(Counter);

🏈 Appendix

Redux の 2 つの Component について

Redux には「Container Component」と「Presentational Component」の 2 種類の Component が存在します。

Container Component

Redux の store のデータを Presentational Component に紐付けるための Component。

import { connect } from "react-redux";
import PostList from "~/components/post/PostList";

const mapStateToProps = state => {
const length = state.posts.length;
const currentState = state.posts[length - 1]; // 一番新しいstateを取り出す
return { posts: currentState.items }; // 描画するのに必要なのはとりあえずitemsだけなのでitemsだけ返す
};

const GetPostList = connect(mapStateToProps)(PostList);

export default GetPostList;

Presentational Component

React の一般的な Component。Container Component で紐付けられたデータを実際に利用して描写する Component。

import React from "react";
import PropTypes from "prop-types";
import Post from "./Post";

const PostList = ({ posts }) => (
<ul>
{posts.map((post, index) => (
<Post key={index} {...post} />
))}
</ul>
);

export default PostList;

🎃 References

🖥 VULTRおすすめ

VULTR」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。 最近はVULTRのヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!

📚 おすすめの書籍