LoginSignup
1
1

More than 3 years have passed since last update.

Reactのアーキテクトを考える道のり - その3(非同期通信)

Last updated at Posted at 2019-09-02

Reduxを使ってコンポーネントの状態を更新できるようになった私は、早速、APIを使って取得した値をコンポーネントに反映させようとしました。

サンプル画面イメージ

作ったサンプルは、3秒待機→https://api.github.com/zen からメッセージを取得してcomponentに反映するだけの簡単なものです。

sampleImage.gif

非同期処理をコンポーネント内にかくと。。。

こんな感じでできます。

import React, { useEffect } from 'react'
import { Typography, Grid, Paper, Link } from '@material-ui/core';
import { sampleSagaActions } from '../../containers/SagaSampleContainer';
import { SampleSagaState } from '../../modules/SampleSaga';
import axios from 'axios';

type props = sampleSagaActions & SampleSagaState;

async function dummyWait() {
    return await new Promise(resolve => {
        const duration = 3000;
        setTimeout(() => {
            resolve({ data: duration });
        }, duration);
    });
}
export const SampleSaga: React.FC<props> = (props) => {
    //ここに画面描画時のAPI取得処理を記述
    useEffect(() => {
        props.setMessage("loading...");
        const initialFunction = async () => {
            //あえて、数十秒止めてLoadingを文字表記
            await dummyWait();
            axios.get('https://api.github.com/zen')
                .then((res) => {
                    props.setMessage(res.data);
                })
        };
        initialFunction();
    }, [])
    return (
        <div>
            <Grid container justify='center' alignItems='center'>
                <Grid item>
                    < Typography variant='h3' color="secondary"> This is a sample page of redux-saga!</Typography >
                </Grid>
                <Grid item>
                    <Grid container alignItems='center'>
                        <Grid item>
                            <Typography variant='h4' style={{ padding: 10 }}>Message from GitHub API is</Typography>
                            <Paper style={{ backgroundColor: '#cfe8fc' }}>
                                <Typography variant='h6'>{props.message}</Typography>
                            </Paper>
                            <Typography variant='h4' style={{ padding: 10 }}>
                                This api calls <Link href='https://api.github.com/zen'>https://api.github.com/zen</Link>
                            </Typography>
                        </Grid>
                    </Grid>
                </Grid>
            </Grid>
        </div>
    )
}

もちろんこれでも動きます:thumbsup:
。。。けど、このままだとコンポーネント単位でのテストをするときに辛くなりそうだし、せっかくRedux使ってコンポーネントからロジックを外に出したのに、ビジネスロジックが入り込みそうな気がします。

というわけで、一般的に使われている非同期処理の作り方を調査してみました。

非同期処理をReduxで実装するときによく使われているライブラリ

調査したところ、Redux-thunkRedux-Sagaが多く使われているようです。
私は比較した結果、Redux-Sagaを採用することにしました。
Redux-thunkはActionに非同期処理を書けるようにするmiddlewareですが、個人的にはActionはStoreを更新するきっかけを定義する役割に限定する方が、システムが大きくなっても、分かり易い設計を保てるのではないかと考えたためです。
ただ、一般的にはRedux-Sagaの方がRedux-thunkに比べて学習コストが高めだと言われる傾向もあるようですので、プロジェクトによってはRedux-thunkを使う選択肢もアリなのかもしれません。

というわけで、Redux-Sagaを使うとこうなった

コンポーネント

import React, { useEffect } from 'react'
import { Typography, Grid, Paper, Link } from '@material-ui/core';
import { sampleSagaActions } from '../../containers/SagaSampleContainer';
import { SampleSagaState } from '../../modules/SampleSaga';

type props = sampleSagaActions & SampleSagaState;

export const SampleSaga: React.FC<props> = (props) => {
    //ここに画面描画時のAPI取得処理を記述
    useEffect(() => {
        props.getMessage()
    }, [])
    return (
        <div>
            <Grid container justify='center' alignItems='center'>
                <Grid item>
                    < Typography variant='h3' color="secondary"> This is a sample page of redux-saga!</Typography >
                </Grid>
                <Grid item>
                    <Grid container alignItems='center'>
                        <Grid item>
                            <Typography variant='h4' style={{ padding: 10 }}>Message from GitHub API is</Typography>
                            <Paper style={{ backgroundColor: '#cfe8fc' }}>
                                <Typography variant='h6'>{props.message}</Typography>
                            </Paper>
                            <Typography variant='h4' style={{ padding: 10 }}>
                                This api calls <Link href='https://api.github.com/zen'>https://api.github.com/zen</Link>
                            </Typography>
                        </Grid>
                    </Grid>
                </Grid>
            </Grid>
        </div>
    )
}

saga

import { put, takeEvery, call } from 'redux-saga/effects'
import { sampleSagaActions } from '../modules/SampleSaga';
import { getGithubApi } from '../API/github';

function dummyWait() {
    return new Promise(resolve => {
        const duration = 3000;
        setTimeout(() => {
            resolve({ data: duration });
        }, duration);
    });
}

function* getMessage() {
    yield put(sampleSagaActions.setMessage("Loading...(3 seconds wait by using setTimeout Function!)"));
    yield call(dummyWait);
    const message: string = yield call(getGithubApi);
    yield put(sampleSagaActions.setMessage(message));
}

// Watch actions
export function* watchApiAsync() {
    yield takeEvery('ACTIONS_GET_MESSAGE', getMessage);
}

module(reducer)


import { reducerWithInitialState } from 'typescript-fsa-reducers';
import actionCreatorFactory from 'typescript-fsa';

const actionCreator = actionCreatorFactory();

export interface SampleSagaState {
    message: string;
}

const initialState: SampleSagaState = {
    message: ''
};

export const sampleSagaActions = {
    getMessage: actionCreator<void>('ACTIONS_GET_MESSAGE'),
    setMessage: actionCreator<string>('ACTIONS_SET_MESSAGE')
};

export const sampleSagaReduecer = reducerWithInitialState(initialState)
    .case(sampleSagaActions.setMessage, (state, message) => {
        const newState = Object.assign({}, state);
        newState.message = message;
        return newState;
    })
    .default((state) => {
        return state
    }
    );

GithubApiを呼び出す処理

import axios from 'axios'
/**
 * 手抜きですが、API取得処理
 */
export const getGithubApi = async () => {
    const response = await axios.get('https://api.github.com/zen');
    return response.data;
}

このようにすることで、
1. コンポーネントはあくまでもActionを呼ぶだけ
2. 非同期処理はSagaで行う

という役割分担が分かり易くできた気がします。

非同期処理を調べる途中で参考にした記事など

  1. ReactのRedux非同期処理がサルでも分かる超解説
  2. reduxで非同期処理をするいくつかの方法(redux-thunk、redux-saga)
  3. redux-sagaで非同期処理と戦う
1
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
1
1