React Reduxでローディングインジケーターを賢く表示:Suspense vs Thunk vs RXJS
React Redux アプリでデータフェッチ中にローディングインジケーターを表示する方法
ローディングステートを管理する
この方法は、Redux ストアを使用してローディングステートを管理します。 以下に、基本的な手順を示します。
- ローディングステート用のアクションを作成する:
const FETCH_DATA_REQUEST = 'FETCH_DATA_REQUEST'; const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS'; const FETCH_DATA_FAILURE = 'FETCH_DATA_FAILURE';
2. Reducer でローディングステートを更新する:
const initialState = {
data: [],
isLoading: false,
error: null
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_DATA_REQUEST:
return { ...state, isLoading: true };
case FETCH_DATA_SUCCESS:
return { ...state, isLoading: false, data: action.payload };
case FETCH_DATA_FAILURE:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
- コンポーネントでローディングステートを使用する:
const MyComponent = () => { const { data, isLoading, error } = useSelector((state) => state); if (isLoading) { return <LoadingIndicator />; // ローディングインジケーターコンポーネントを表示 } if (error) { return <ErrorMessage error={error} />; // エラーメッセージを表示 } return ( <div> {data.map((item) => ( <Item key={item.id}>{item.name}</Item> ))} </div> ); };
React Suspense を使用する
React Suspense は、データフェッチが完了するまでコンポーネントのレンダリングをブロックする新しい機能です。 以下に、基本的な手順を示します。
-
Suspense
コンポーネントで囲む:<Suspense fallback={<LoadingIndicator />}> <MyComponent /> </Suspense>
上記以外にも、Redux Thunk
や RXJS
などのライブラリを使用してローディングインジケーターを実装する方法があります。
- src/
- actions.js
- reducer.js
- App.js
- LoadingIndicator.js
- index.js
actions.js
export const fetchDataRequest = () => ({ type: FETCH_DATA_REQUEST });
export const fetchDataSuccess = (data) => ({ type: FETCH_DATA_SUCCESS, payload: data });
export const fetchDataFailure = (error) => ({ type: FETCH_DATA_FAILURE, payload: error });
reducer.js
import { FETCH_DATA_REQUEST, FETCH_DATA_SUCCESS, FETCH_DATA_FAILURE } from './actions';
const initialState = {
data: [],
isLoading: false,
error: null
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_DATA_REQUEST:
return { ...state, isLoading: true };
case FETCH_DATA_SUCCESS:
return { ...state, isLoading: false, data: action.payload };
case FETCH_DATA_FAILURE:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
export default reducer;
App.js
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchDataRequest } from './actions';
import LoadingIndicator from './LoadingIndicator';
import Item from './Item';
const App = () => {
const dispatch = useDispatch();
const { data, isLoading, error } = useSelector((state) => state);
React.useEffect(() => {
dispatch(fetchDataRequest());
}, []);
if (isLoading) {
return <LoadingIndicator />;
}
if (error) {
return <ErrorMessage error={error} />;
}
return (
<div>
{data.map((item) => (
<Item key={item.id}>{item.name}</Item>
))}
</div>
);
};
export default App;
LoadingIndicator.js
import React from 'react';
const LoadingIndicator = () => (
<div className="loading-indicator">
<p>読み込み中...</p>
</div>
);
export default LoadingIndicator;
Item.js
import React from 'react';
const Item = ({ id, name }) => (
<li key={id}>
{name}
</li>
);
export default Item;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
store.js
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
export default store;
このコードの説明:
- actions.js: データフェッチに関するアクションを定義します。
- reducer.js: アプリケーションの状態を管理するReducerを定義します。
- App.js: アプリケーションのメインコンポーネントです。
useDispatch
とuseSelector
フックを使用して、Redux ストアとのやり取りを行います。 - LoadingIndicator.js: データフェッチ中に表示されるローディングインジケーターコンポーネントです。
- Item.js: アプリケーションで表示されるデータ項目コンポーネントです。
- index.js: アプリケーションのルートコンポーネントです。 Redux ストアと
App
コンポーネントを Provider コンポーネントでラップします。 - store.js: Redux ストアを作成します。
- コードがより簡潔になる
- ローディングステートを明示的に管理する必要がない
useQuery
フックと Suspense
コンポーネントを使用して、React Suspense でローディングインジケーターを実装できます。
例:
import { useQuery } from 'react-query';
import { Suspense } from 'react';
const MyComponent = () => {
const { data, isLoading, error } = useQuery('data', fetch('/api/data'));
if (isLoading) {
return <LoadingIndicator />;
}
if (error) {
return <ErrorMessage error={error} />;
}
return (
<div>
{data.map((item) => (
<Item key={item.id}>{item.name}</Item>
))}
</div>
);
};
const App = () => (
<Suspense fallback={<LoadingIndicator />}>
<MyComponent />
</Suspense>
);
Redux Thunk を使用する
Redux Thunk
は、非同期アクションを処理するためのミドルウェアです。 Redux Thunk
を使用して、ローディングステートを管理し、ローディングインジケーターを表示できます。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';
const store = createStore(reducer, applyMiddleware(thunk));
const fetchData = () => async (dispatch) => {
dispatch({ type: FETCH_DATA_REQUEST });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: FETCH_DATA_SUCCESS, payload: data });
} catch (error) {
dispatch({ type: FETCH_DATA_FAILURE, payload: error });
}
};
store.dispatch(fetchData());
RXJS を使用する
RXJS
は、非同期データストリームを処理するためのライブラリです。 RXJS
を使用して、ローディングステートを管理し、ローディングインジケーターを表示できます。
import { Observable } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { createStore } from 'redux';
import reducer from './reducer';
const store = createStore(reducer);
const fetchDataEpic = (action$) =>
action$.pipe(
ofType(FETCH_DATA_REQUEST),
switchMap(() =>
Observable.fromFetch('/api/data')
.pipe(
map((response) => response.json()),
map((data) => ({ type: FETCH_DATA_SUCCESS, payload: data })),
catchError((error) => ({ type: FETCH_DATA_FAILURE, payload: error }))
)
)
);
const epics = [fetchDataEpic];
createEpicStore({
reducer,
epics,
store: store
});
javascript reactjs redux