Reactで非同期データフェッチを賢く使う!useReducer フックとuseEffect フックのベストプラクティス
React useReducer で非同期データフェッチを行う
React の useReducer
フックと非同期処理を組み合わせることで、API からデータをフェッチし、アプリケーションの状態を更新することができます。この方法は、useState
フックよりも複雑な状態管理に適しています。
手順
- useReducer フックを使用する:
- ステートとアクションディスパッチャを初期化する
- reducer 関数を作成する:
- 非同期処理を行うアクションを作成する:
useEffect
フックまたはカスタムフックを使用して、API フェッチを実行する- フェッチが成功したら、アクションディスパッチャを使用してステートを更新する
- コンポーネント内でステートとアクションを使用する:
- ステートをレンダリングし、アクションを dispatch する
例
import React, { useReducer } from 'react';
const initialState = {
loading: true,
data: null,
error: null,
};
const reducer = (state, action) => {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true };
case 'FETCH_SUCCESS':
return { loading: false, data: action.payload, error: null };
case 'FETCH_ERROR':
return { loading: false, data: null, error: action.payload };
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
dispatch({ type: 'FETCH_START' });
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then((response) => response.json())
.then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
.catch((error) => dispatch({ type: 'FETCH_ERROR', payload: error }));
}, []);
if (state.loading) {
return <div>Loading...</div>;
}
if (state.error) {
return <div>Error: {state.error.message}</div>;
}
return <div>{state.data.title}</div>;
};
export default App;
補足
- 上記の例では、
useEffect
フックを使用して非同期処理を行っています。 - カスタムフックを使用して非同期処理をカプセル化することもできます。
useReducer
フックは、複雑な状態管理をシンプルかつ効率的に行うのに役立ちます。
サンプルコードの詳細解説
useReducer フックの初期化
const initialState = {
loading: true,
data: null,
error: null,
};
const [state, dispatch] = useReducer(reducer, initialState);
initialState
オブジェクトは、ステートの初期値を定義します。loading
: データの読み込み中かどうかを示すフラグdata
: API から取得したデータerror
: エラーが発生した場合はその内容
useReducer
フックを使用して、reducer
関数とinitialState
を元にステートとアクションディスパッチャを初期化します。
reducer 関数
const reducer = (state, action) => {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true };
case 'FETCH_SUCCESS':
return { loading: false, data: action.payload, error: null };
case 'FETCH_ERROR':
return { loading: false, data: null, error: action.payload };
default:
return state;
}
};
reducer
関数は、アクションの種類に応じてステートを更新するロジックを記述します。FETCH_START
: データの読み込みを開始したときに呼び出され、loading
フラグをtrue
に設定します。FETCH_SUCCESS
: データの読み込みが成功したときに呼び出され、loading
フラグをfalse
に設定し、data
プロパティに取得したデータを格納します。default
: アクションの種類が不明な場合は、現在のステートをそのまま返します。
非同期処理を行うアクションの作成
useEffect(() => {
dispatch({ type: 'FETCH_START' });
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then((response) => response.json())
.then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
.catch((error) => dispatch({ type: 'FETCH_ERROR', payload: error }));
}, []);
useEffect
フックを使用して、非同期処理を実行します。dispatch({ type: 'FETCH_START' })
: データの読み込みを開始するアクションを dispatch します。fetch('https://jsonplaceholder.typicode.com/todos/1')
: 指定されたURLからAPIをフェッチします。then((response) => response.json())
: フェッチが成功した場合は、レスポンスをJSON形式に変換します。then((data) => dispatch({ type: 'FETCH_SUCCESS', payload: data }))
: データの読み込みが成功した場合は、FETCH_SUCCESS
アクションを dispatch し、payload
プロパティに取得したデータを格納します。
[]
を空配列として渡すことで、コンポーネントのマウント時に一度だけ実行されます。
コンポーネント内でステートとアクションを使用する
if (state.loading) {
return <div>Loading...</div>;
}
if (state.error) {
return <div>Error: {state.error.message}</div>;
}
return <div>{state.data.title}</div>;
- ステートの
loading
プロパティがtrue
の場合は、"Loading..." というメッセージを表示します。 - ステートの
error
プロパティがnull
ではない場合は、エラーメッセージを表示します。 - それ以外の場合は、ステートの
data
プロパティのtitle
プロパティの値を表示します。
- 上記の例では、
https://jsonplaceholder.typicode.com/todos/1
という架空のAPIを使用しています。実際のアプリケーションでは、使用するAPIに合わせてURLを変更する必要があります。 - エラー処理をより詳細に行う場合は、
error
プロパティにエラーコードや詳細なメッセージなどを格納
React で非同期データフェッチを行うその他の方法
useState フックと useEffect フック
- 最もシンプルで分かりやすい方法です。
- 小規模なアプリケーションや単純なデータフェッチに適しています。
import React, { useState, useEffect } from 'react';
const App = () => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error));
}, []);
if (error) {
return <div>Error: {error.message}</div>;
}
if (!data) {
return <div>Loading...</div>;
}
return <div>{data.title}</div>;
};
export default App;
カスタムフック
- 複雑な非同期処理をカプセル化したい場合に適しています。
- コードの再利用性と保守性を向上させることができます。
import React, { useState } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error));
}, [url]);
return { data, error };
};
const App = () => {
const { data, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
if (error) {
return <div>Error: {error.message}</div>;
}
if (!data) {
return <div>Loading...</div>;
}
return <div>{data.title}</div>;
};
export default App;
サードパーティライブラリ
react-redux
やMobX
などのライブラリを使用すると、より複雑な状態管理を行うことができます。
サスペンス
- React 18 で導入された新しい機能です。
- データが読み込まれるまでコンポーネントをブロックすることができます。
import React, { useState, suspense } from 'react';
const App = () => {
const [data, setData] = useState(null);
const fetchData = async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = await response.json();
setData(data);
};
return (
<React.Suspense>
{fetchData()}
{data ? <div>{data.title}</div> : null}
</React.Suspense>
);
};
export default App;
それぞれの方法のメリットとデメリット
方法 | メリット | デメリット |
---|---|---|
useState フックと useEffect フック | シンプルで分かりやすい | 複雑な状態管理には不向き |
カスタムフック | 複雑な非同期処理をカプセル化できる | コード量が増える |
サードパーティライブラリ | 複雑な状態管理が可能 | 学習コストが高い |
サスペンス | データの読み込み状況を明確に表現できる | React 18 以降でしか利用できない |
どの方法を使用するかは、アプリケーションの要件や開発者の好みによって異なります。
- シンプルで分かりやすい方法を求めている場合は、
useState
フックとuseEffect
フックを使用するのがおすすめです。 - 複雑な非同期処理をカプセル化したい場合は、カスタムフックを使用するのがおすすめです。
- 大規模なアプリケーションや複雑なデータフローを持つ場合は、サードパーティライブラリを使用するのがおすすめです。
- 最新の機能を利用したい場合は、サスペンスを使用するのがおすすめです。
reactjs react-hooks