React での非同期処理
Reactでの非同期処理の歴史
- コンポーネントのライフサイクルを使用
- Reduxミドルウェアを使用
- useEffect Hooks を使用
コンポーネントのライフサイクルを使用
Reactがリリースされた当初は、componentDidMount
といったReact のライフサイクルを利用し、非同期処理を実施していました。
class App extends React.Component {
constructor(props) {
super(props);
this.state = { hits: [] };
}
async componentDidMount() {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);
this.setState({ hits: result.data });
});
return (
<ul>
{data.hits.map(item => (
<li key={item.objectID}>
<a href={item.url}>{item.title}</a>
</li>
))}
</ul>
);
}
デメリット
- ロジックが追加されるごとに、コンポーネントが肥大化していく
Redux Middleware
その後、Redux
がグローバルな状態管理ライブラリとして普及していくと、データフローの一環として、Redux
に非同期処理も任せるようになっていきました。
Redux
にはMiddleware
といった機能が搭載されており、Reducer
の実行前後に任意の処理を追加することができました。
Middleware
ライブラリとして Redux Thunk
や Redux Saga
などが利用されていました。
import { Action, Dispatch } from 'redux';
import { ThunkAction } from 'redux-thunk';
import { AppState } from './store';
const initialState = {};
// Reducerの定義
export default function todosReducer(state = initialState, action: Action<string>) {
switch (action.type) {
case 'todos/todosLoaded':
return {
...state,
todos: action.payload
};
default:
return state;
}
}
// Thunk function
export const fetchTodos = (): ThunkAction<void, AppState, null, Action<string>> => async (dispatch: Dispatch, getState: () => AppState) => {
try {
const response = await client.get('/fakeApi/todos');
dispatch({ type: 'todos/todosLoaded', payload: response.todos });
} catch (error) {
console.error('Failed to fetch todos:', error);
}
}
デメリット
- 学習コストが高い
- ボイラープレートが多い
- Storeの構造が過度に複雑化
- 使用するミドルウェアでコミュニティが分断される
useEffect Hooks
React 16.8
から、非同期処理の課題を解決するためのソリューションが公式によって追加されました。
それが useEffect Hooks
です。
useEffect の中で非同期処理を定義することでコンポーネントの肥大化を防ぐことができるようになりました。
import { FC, useEffect, useState } from 'react';
import { requestPath, weatherConditionsMap } from './data';
import { Weather } from './domain/type';
const SampleAsynchronous: FC = () => {
const [weather, setWeather] = useState<Weather>({} as never);
useEffect(() => {
const getTodayWeather = () => {
fetch(requestPath)
.then((data) => data.json())
.then((json) => {
setWeather((state) => ({
...state,
code: weatherConditionsMap[json.current.weather_code],
temperature: json.current.temperature_2m,
}));
});
};
void getTodayWeather();
}, []);
return (
<>
<h1>Tokyo Weather</h1>
{weather.code ? (
<>
<div>{`現在の東京の天気は${weather.code}です`}</div>
<div>{`温度: ${weather.temperature}℃`}</div>
</>
) : (
'data loading...'
)}
</>
);
};
export default SampleAsynchronous;
詳細コード: https://github.com/PenPeen/react_practice/pull/43
まとめ
非同期処理も扱いやすくなっていることがわかりますね...