はじめに
先日の記事で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、ドキュメントサイトのチュートリアルで紹介するには向かず、シンプルな非同期処理のみを対象にした本ライブラリを開発しました。
-
Reduxの公式ドキュメントでも、redux-sagaは標準採用ではなく、redux-thunkが標準になっています。 ↩