12
11

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.

はじめに

先日の記事react-trackedの紹介をしました。react-trackedはreactのcontextとhooksを使ったglobal stateのライブラリです。プリミティブな機能を提供しており、必要に応じて拡張(custom hooks化など)して使えます。非同期処理も可能なのですが、ドキュメントサイトでチュートリアルを作成する際には、オススメの非同期処理なども記述する必要があると思いました。react-trackedはreduxなどと違って外部にstoreを持たないため、非同期処理もReactの範囲でやることになります。custom hooksで非同期処理用のcallbackを作る方法もありますが、今回は、useReducerを拡張して非同期処理を記述するライブラリを紹介します。

react-trackedと合わせて使うことを想定して説明しましたが、特にreact-tracked専用のライブラリではなく、普通のReact stateでも使うことができます。

use-reducer-async

リポジトリはこちらです。

コードはとても小さいです。ライブラリにしなくても自分でcustom hooksを書いても同じことができます。このライブラリは、その機能より、コーディングパターンを提案することに意味があります。

使い方

一例として、データ取得を行うケースを実装してみたいと思います。

ライブラリをインポートします。一つのhookだけです。

import { useReducerAsync } from 'use-reducer-async';

初期ステートを定義します。personというデータを取得するケースを想定しています。

const initialState = {
  loading: false,
  person: null,
};

reducerを定義します。

const reducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_STARTED': return { ...state, loading: true };
    case 'FETCH_FINISHED': return { ...state, loading: false, person: action.person };
    default: throw new Error('no such action type');
  }
};

非同期処理を行うハンドラーを定義します。今回は一つだけです。

const asyncActionHandlers = {
  START_FETCH: (dispatch, getState) => async (action) => {
    dispatch({ type: 'FETCH_STARTED' });
    const data = await fetchData();
    dispatch({ type: 'FETCH_FINISEHD', person: data });
  },
};

最後にコンポーネントでhookを使います。

const Component = () => {
  const [state, dispatch] = useReducerAsync(reducer, initialState, asyncActionHandlers);
  return (
    <div>
      <button type="button" onClick={() => dispatch({ type: 'START_FETCH' })}>Fetch Person</button>
      {state.loading && 'Loading...'}
      {state.person && <Person person={state.person} />}
    </div>
  );
};

これにより非同期処理が実行されて、ステートが更新されます。

ポイント

今回の例では使いませんでしたが、本ライブラリはTypeScriptの型定義があるため、それを使うとより便利です。

ところで、Reduxでは公式ドキュメントでredux-thunkを推奨していますが、型付けが難しいことが課題です。use-reducer-asyncやcustom hooksで非同期処理をする場合は、その課題が軽減されます。ちなみに、本ライブラリのハンドラー定義はredux-thunkのAPIとそっくりです。

また、Reduxの場合はexternal storeを使うため、Concurrent Modeの対応が限定的になる部分があります。use-reducer-asyncはReactのstateを使うだけなので、Concurrent Modeに素直に対応できます。

おわりに

このライブラリを作ろうと思ったきっかけは、use-saga-reducerを見つけたからです。redux-sagaにExternal APIというものがあることを知らず、Redux以外で使えることは驚きでした。redux-sagaはとても強力でいいとは思うのですが1、ドキュメントサイトのチュートリアルで紹介するには向かず、シンプルな非同期処理のみを対象にした本ライブラリを開発しました。

  1. Reduxの公式ドキュメントでも、redux-sagaは標準採用ではなく、redux-thunkが標準になっています。

12
11
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
12
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?