はじめに
React 16.8から導入されたhooksにはuseEffectがあります。
詳細は公式サイトをまず参照しましょう。
useEffectを使うと、コンポーネントのレンダリングとは別に処理を書くことができます。useEffectでしばしば非同期処理を書くことがあります。例えば、サーバからのデータ取得の処理などがあります。
以下では、useEffectで非同期処理を書く場合の注意点を2つ紹介します。ケースによっては注意点はこの2つだけではない可能性が高いので、ご留意ください。
promiseを返さない
useEffectに渡す関数の戻り値はcleanup関数です。
useEffect(() => {
console.log('side effect!');
const cleanup = () => {
console.log('cleanup!');
};
return cleanup;
}, []);
cleanup関数は次のeffectが呼ばれる前やアンマウントする場合に呼ばれます。(depsが[]
なのでこの例では後者のみ)
よって、下記は間違いです。
useEffect(async () => {
await new Promise(r => setTimeout(r, 1000));
console.log('side effect!');
}, []);
このコードはcleanup関数の代わりにpromiseを返してしまっています。
正しくは、下記のようにします。
const sleep = ms => new Promise(r => setTimeout(r, ms));
useEffect(() => {
const f = async () => {
await new Promise(r => setTimeout(r, 1000));
console.log('side effect!');
};
f();
}, []);
アンマウントのフラグを持つ
非同期処理を書く場合、コンポーネントが削除された後にコールバックが呼ばれる場合があります。この時、コンポーネントのステートを変更しようとするとワーニングがでます。
(追記2021/8/20: このワーニングは、今後でなくなります。ワーニングを消す目的でアンマウントのフラグを持つのはそもそも回避策であり、今後はこの対応は非推奨になります。参考)
const [count, setCount] = useState(0);
useEffect(() => {
const f = async () => {
await new Promise(r => setTimeout(r, 1000));
setCount(c => c + 1);
};
f();
}, []);
これを回避するには次のようにアンマウントのフラグを持ちます。
const [count, setCount] = useState(0);
useEffect(() => {
let unmounted = false;
const f = async () => {
await new Promise(r => setTimeout(r, 1000));
if (!unmounted) {
setCount(c => c + 1);
}
};
f();
const cleanup = () => {
unmounted = true;
};
return cleanup;
}, []);
おわりに
React HooksのuseEffectについて非同期処理を使う場合のよくあるケースの注意点について紹介しました。React Hooksはまだベストプラクティスが溜まっていないため、今後違う方法が主流になる可能性はある点についてはご注意ください。