非同期処理はイベントハンドラーで書いてもいい
初心者の自分としては迷うところだったので,備忘録を兼ねて記事を書きました.
Reactコンポーネント内での非同期処理
Reactではコンポーネントを宣言的に記述します.コンポーネントはレンダリング処理に関する操作(副作用)がない純粋関数として定義することが推奨されています.しかし実際はコンポーネントのイベント処理などでデータフェッチやDOM操作などレンダリング処理以外の操作が必要となることが少なくありません.
例えばボタンを押したときにあるサーバーからデータをフェッチするコンポーネントがあるとします.データフェッチなどの非同期処理はコンポーネントのレンダリングに直接関係のある処理ではないため,これらは副作用にあたります.
useEffect
このような副作用の処理はuseEffect
内で記述して実行することが可能です.useEffect
ではコンポーネントのライフサイクルとは別に,特定の変数を監視対象とすることで,値の変更をトリガーとしてそのコードブロック内の処理を実行することができます.
何かがおかしい
ここで「useEffect
では副作用を記述することができます」という記事を読んだ私は「非同期処理は副作用だからuseEffect
で書くんだな!」と思い込みました.そして以下のようなコードを書いていました.
function MyButton(): React.JSX.Element {
const [state, setState] = useState<boolean>(false);
useEffect(() => {
const func = async () => {
const res = await fetch("hoge");
// ...
};
func();
}, [state]);
function handleClick() {
setState((state) => !state);
}
return <button type="button" onClick={handleClick}>Click!</button>
}
あれ...こんなにややこしく書かないといけないんだっけ...?
イベントハンドラー
さすがに何かおかしいと思った私は,本当に副作用はuseEffect
にしか書かないといけないのか調べました.
Should I use useEffect or a handler for async calls? : r/reactjs
どちらの記事にも書かれているように.副作用である非同期処理はイベントハンドラー内で書けます.そもそも,イベント自体がコンポーネントの描画とは関係のない処理であり,副作用そのものです.イベントハンドラー内で副作用を書くほうが自然に感じます.
イベントハンドラー自体をasync
にして書き換えると以下のようになりました.こちらの方がコードもすっきりしていて良いですね.
function MyButton(): React.JSX.Element {
async function handleClick() {
const res = await fetch("hoge");
// ...
}
return <button type="button" onClick={handleClick}>Click!</button>
}
使い分け
ではuseEffect
はどのようなときに使用すればよいのでしょうか.先にあげた2つの記事によると,基本的にはイベントハンドラーを扱い,それでは対応できない特殊なケースでuseEffect
を使うのが良いようです.
- ボタンクリックなどのイベントがトリガーになる処理はイベントハンドラーに書く.
- レンダリングやステートの変更をトリガーとしたい処理は
useEffect
に書く. - クリーンアップ処理が必要な場合は
useEffect
に書く.
まとめ
副作用を起こす処理がイベント駆動の場合は,イベントハンドラーに書きましょう.そうでなくレンダリングのタイミングに依存する場合はuseEffect
を使うことができます.
Discussion