LoginSignup
0
1

More than 3 years have passed since last update.

React + Redux でミドルウェアを使わないシンプルな非同期処理

Posted at

はじめに

React.useEffectのみを持つコンポーネント(APIコンポーネント)を利用することで、非同期処理を行う方法を紹介します。APIコンポーネントを用いることで、非同期処理を含めたデータの流れがとてもシンプルになります。

APIコンポーネントとは

特徴

  1. propsを受けとらない
  2. Elementを返さない
  3. Redux Storeのみとデータのやり取りを行う
  4. APIReducer(APIコンポーネント専用のReducer)が管理するプロパティの更新を検知して非同期処理を開始する

SampleAPI.tsx

import * as React from 'react';
import { Dispatch, Action } from 'redux';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from 'store';

export const SampleAPI: React.FC<{}> = () => { // 1. propsを受けとらない
  /*
    3. Redux Storeのみとデータのやり取りを行う
  */
  const dispatch = useDispatch<Dispatch<Action>>();

  const data = useSelector<RootState, any>(
    state => state.sampleAPI.data
  );

  React.useEffect(() => {
    /*
      非同期処理
    */
  }, [data]); // 4. APIReducerが管理するプロパティの更新を検知して非同期処理を開始する

  return null; // 2. Elementを返さない
};

以降、最小構成のツイートアプリを用いて具体例を紹介します。 ソースコード(GitHub)

ツイート全体の取得 (UpdateTweetsAPI)

データの流れ

update_tweets.png
1. tweetsAPIReducerが管理するupdatingプロパティの更新をUpdateTweetsAPIが検知
2. UpdateTweetsAPIはサーバからツイート全体を取得
3. UpdateTweetsAPIはentitiesReducerが管理するtweetsプロパティに取得したツイート全体を保存
4. TweetListコンポーネントはtweetsプロパティの更新に伴って再描画。ツイートの一覧を表示

UpdateTweetsAPI.tsx

import * as React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Dispatch, Action } from 'redux';
import { RootState } from 'store';
import { entitiesActions } from 'actions/entitiesActions';
import { tweetsAPIActions } from 'actions/tweetsAPIActions';

/*
  サーバAPIとの通信用クライアント
*/
const fetchTweets = () => fetch('http://localhost/tweets', {
  method: 'GET',
  mode: 'cors',
  credentials: 'include',
});

export const UpdateTweetsAPI: React.FC<{}> = () => {
  const dispatch = useDispatch<Dispatch<Action>>();

  /*
    tweetsAPIReducerが管理するupdatingプロパティ(boolean型)
    非同期処理開始のトリガーとなるAPIReducerが管理するプロパティ
  */
  const updating = useSelector<RootState, boolean>(
    (state) => state.tweetsAPI.updating,
  );

  React.useEffect(() => {
    if (!updating) return;

    // 2. サーバからツイート全体を取得
    fetchTweets()
      .then((res) => res.json())
      .then((res) => {
        if (!res.tweets) return;

        // 3. entitiesReducerが管理するtweetsプロパティに取得したツイート全体を保存
        dispatch(entitiesActions.updateTweets(res.tweets));
      })
      .then(() => {
        /*
          非同期処理の終了をディスパッチ
          データの流れでは省略
        */
        dispatch(tweetsAPIActions.updateTweetsDone());
      })
      .catch(() => {
        dispatch(tweetsAPIActions.updateTweetsDone());
      });
  }, [updating]); // 1. updatingプロパティの更新を検知

  return null;
};

ツイートの送信 (SendTweetAPI)

データの流れ

send_tweet.png
1. TweetFormコンポーネントで送信ボタンが押されると、フォームの内容をtweetsAPIReducerが管理するnewContentプロパティに保存
2. newContentプロパティの更新をSendTweetAPIが検知
3. SendTweetAPIはサーバへツイートを送信
4. SendTweetAPIはupdateTweetsアクションをディスパッチすることで、tweetsAPIReducerが管理するupdatingプロパティを更新
5. updatingプロパティの更新をUpdateTweetsAPIが検知 (以後、上記のツイート全体の取得)

SendTweetAPI.tsx

import * as React from 'react';
import { Dispatch, Action } from 'redux';
import { useSelector, useDispatch } from 'react-redux';
import { RootState } from 'store';
import { tweetsAPIActions } from 'actions/tweetsAPIActions';

/*
  サーバAPIとの通信用クライアント
*/
const sendTweet = (content: string) => fetch('http://localhost/tweets', {
  method: 'POST',
  mode: 'cors',
  credentials: 'include',
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ content }),
});

export const SendTweetAPI: React.FC<{}> = () => {
  const dispatch = useDispatch<Dispatch<Action>>();

  /*
    tweetsAPIReducerが管理するnewContentプロパティ(string型)
    非同期処理開始のトリガーとなるAPIReducerが管理するプロパティ
  */
  const newContent = useSelector<RootState, string>(
    (state) => state.tweetsAPI.newContent,
  );

  React.useEffect(() => {
    if (newContent === '') return;

    // 3. サーバへツイートを送信
    sendTweet(newContent)
      .then(() => {
        /*
          4. SendTweetAPIはupdateTweetsアクションをディスパッチすることで、
             tweetsAPIReducerが管理するupdatingプロパティを更新
        */
        dispatch(tweetsAPIActions.updateTweets());
      })
      .then(() => {
        /*
          非同期処理の終了をディスパッチ
          データの流れでは省略
        */
        dispatch(tweetsAPIActions.sendTweetDone());
      })
      .catch(() => {
        dispatch(tweetsAPIActions.sendTweetDone());
      });
  }, [newContent]); // 2. newContentプロパティの更新を検知

  return null;
};

おわりに

APIコンポートを利用すると、非同期処理をReduxデータフローの中に組み込むことができます。
また、以下のようにコンポーネントとして配置するだけで、APIコンポートはそのまま機能します。

/* TweetPanel.tsx */
import * as React from 'react';
import { TweetForm } from 'containers/TweetFormCTR';
import { TweetList } from 'containers/TweetListCTR';
import { UpdateTweetsAPI } from 'api/UpdateTweetsAPI';
import { SendTweetAPI } from 'api/SendTweetAPI';

export const TweetPanel: React.FC<{}> = () => (
  <div>
    <TweetForm />
    <TweetList />
    <UpdateTweetsAPI />
    <SendTweetAPI />
  </div>
);

再掲: 最小構成のツイートアプリ(GitHub)

0
1
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
0
1