useEffect フックを使いこなして、React.js アプリケーションを安全に開発しよう
React.js の useEffect フックにおける無限ループ
無限ループが発生する原因
useEffect
フック内で状態変数を更新すると、コンポーネントが再レンダリングされます。そして、再レンダリングされると、useEffect
フックが再度呼び出され、また状態変数を更新... というように、無限ループに陥ってしまうのです。
無限ループを防ぐためには、以下の方法があります。
関数の定義をコンポーネントの外に移す
useEffect
フック内で使用する関数をコンポーネントの外に定義することで、毎回新しい関数が作成されるため、無限ループを防ぐことができます。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
第2引数に空の配列を渡す
useEffect
フックの第2引数に空の配列を渡すと、最初のレンダリング時のみ実行されます。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
状態変数の依存関係を指定する
useEffect
フックの第2引数に、依存関係となる状態変数を指定することで、その状態変数が更新されたときのみ実行されます。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
その他の注意点
useEffect
フック内で状態変数を更新する必要がある場合は、必ず関数内で更新するようにしましょう。useEffect
フック内で非同期処理を行う場合は、必ずクリーンアップ関数を使ってリソースを解放するようにしましょう。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
このコードを実行すると、コンポーネントがレンダリングされるたびに setCount
関数が呼び出され、count
状態変数が1ずつ増えていきます。そして、count
状態変数が更新されると、コンポーネントが再レンダリングされます。これが無限ループに陥る原因です。
無限ループを防ぐ方法
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
function incrementCount() {
setCount(count + 1);
}
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
useEffect
フックは便利なツールですが、使い方を誤ると無限ループが発生してしまうことがあります。上記のサンプルコードを参考に、無限ループを防ぐ方法を理解しておきましょう。
useEffect フックにおける無限ループを防ぐ方法
useRef
Hook を使用して、前のレンダリング時の状態変数を保持することができます。
function MyComponent() {
const [count, setCount] = useState(0);
const previousCount = useRef(0);
useEffect(() => {
if (count !== previousCount.current) {
// 何か処理を行う
previousCount.current = count;
}
}, [count]);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
このコードでは、previousCount
という useRef
Hook を使用して、前のレンダリング時の count
状態変数を保持しています。useEffect
フック内で、現在の count
状態変数と previousCount
を比較し、異なっていた場合のみ処理を行います。
useCallback
Hook を使用して、依存関係のない関数を生成することができます。
function MyComponent() {
const [count, setCount] = useState(0);
const incrementCount = useCallback(() => {
setCount(count + 1);
}, []);
useEffect(() => {
incrementCount();
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
このコードでは、incrementCount
という useCallback
Hook を使用して、依存関係のない関数を作成しています。useEffect
フック内で、incrementCount
関数を呼び出すことで、無限ループを防ぐことができます。
状態変数を直接更新するのではなく、setCount
関数のような更新関数を介して更新するようにしましょう。
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
// count を直接更新するのではなく、setCount 関数を使用する
setCount(count => count + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<h1>カウント: {count}</h1>
</div>
);
}
このコードでは、count
状態変数を直接更新するのではなく、setCount
関数を使用して更新しています。setCount
関数は内部的に前の状態変数を参照するため、無限ループを防ぐことができます。
reactjs react-hooks