21
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

reduxでredux-thunkとtypescript-fsaで非同期処理をする

Posted at

reduxで非同期処理は、redux-sagaとかいろいろ設計があるけど、自分としてredux-thunkでやるのが理解しやすい。
また、Typescriptでそのままredux使おうとすると、型定義でつらいことになりがちなので、typescript-fsaを使うといいらしい。

コード

storeの定義

  • redux-thunkを入れる
store.ts
import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";

import * as doc from "./states/doc";

export interface RootStore {
    doc: doc.State
}

const store = createStore(
    combineReducers<RootStore>({
        doc: doc.reducer
    }),
    {},
    applyMiddleware(thunk)
);

export default store;

actionの定義

  • /actionsディレクトリ以下に置いてる
  • typescript-fsaでActionの定義をする
  • actionCreator.asyncを利用することで、非同期処理に使いそうなAction(started,done,failed)を定義してくれるので、非同期処理の状態(例ではfetchの開始・完了・失敗)に応じて、dispatchしてあげればよい
  • redux-thunkを使うのでAction Creator(下記loadAllDoc)は関数を返す
    • Promiseを返すようにしておくと、dispatchする側から結果の取得ができ便利
actions/doc.ts
import { Dispatch } from "redux";
import actionCreatorFactory from "typescript-fsa";

import { RootStore } from "../store";

// データの型定義
export interface Doc {
    id: string; 
    // ...
}

// typescript-fsaでActionの定義 
const actionCreator = actionCreatorFactory();
export const actions = {
    loadAllDocs: actionCreator.async<{}, Doc[]>("LOAD_ALL_DOCS"),
};

// redux-thunkを使うためActionは関数
export function loadAllDoc() {
    return (dispatch: Dispatch<RootStore>, getState: () => RootStore) => {
        return new Promise<Doc>((resolve, reject) => {
            // 開始
            dispatch(actions.loadAllDocs.started({params: {}}));
            fetch("http://....")
                .then(res => {
                    return res.json()
                        .then(json => {
                            if(res.ok) {
                                // 成功
                                dispatch(actions.loadAllDocs.done({result: json, params: {}}));
                                resolve(json);
                            } else {
                                // 失敗
                                dispatch(actions.loadAllDocs.failed({error: json, params: {}}));
                                reject(new Error("..."));
                            }
                        });
                }).catch(error => {
                    // 失敗
                    dispatch(actions.loadAllDocs.failed({error: error, params: {}}));
                    reject(error);
                });
        });
    }
}


stateの定義

  • /statesディレクトリ以下に置いてる
  • typescript-fsaを使うとactionで定義した型がreducerの定義時に使える
  • 非同期処理用の各Action(started,done,failed)ごとにstate を更新する
    • 非同期API呼び出しでは、たいていloading状態や失敗の状態の管理もいるので、StoreData<T>のようなものを定義して、状態の表現とデータをまとめてる
states/doc.ts
import { reducerWithInitialState } from "typescript-fsa-reducers";
import { actions, Doc } from "../actions/doc";

// 非同期の取得状態を表現するための型
export interface StoreData<T> {
    data: T;
    loading?: boolean;
    error?: any;
}

export interface State {
    all: StoreData<Doc[]>;
}

const initialState: State = {
    all: {
        data: [],
        loading: false
    }
};

// typescript-fsaでreducerの定義 
// started,done,failed でstate をそれぞれ更新
export const reducer = reducerWithInitialState(initialState)
    .case(actions.loadAllDocs.started, (state, payload) => {
        return { ...state, all: { data: [], loading: true, error: null } };
    }).case(actions.loadAllDocs.done, (state, payload) => {
        return { ...state, all: { data: payload.result, loading: false, error: null } };
    }).case(actions.loadAllDocs.failed, (state, payload) => {
        return { ...state, all: { data: [], loading: false, error: payload.error } };
    });

呼び出し

  • reactから呼ぶ、react-reduxでconnectしておく
view.tsx

class View extends React.Component<{}, {}> {

    componentDidMount() {
        // actionをdispatchする
        // redux-thunkでPromiseを返すように定義したので、ここの戻り値がそのPromiseになる
        // Promiseの結果で何か(localなステートに何かする等)できる
        this.props.dispatch(loadAllDoc())
            .then((all) => {
                // 必要なら成功したら画面遷移とか
                // this.props.history.replace(`/docs`);
            }).catch((error) => {
                // 必要なら失敗の通知とか
                // toast("....")
            })
    }

    render() {
        const { doc } = this.props;
        if(doc.all.loading) {
            // loading状態
            return <div>loading</div>
        } else if(doc.all.error){
            // 失敗した状態(エラー表示)
            return ...
        } else {
            // doc.dataを表示
            return ...
        }
    }
}
21
10
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
21
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?