この記事ではReactフックの1つである『useCallback』について、
useCallback
とはuseCallback
の構文useCallback
の使い方useCallback
の依存配列を指定する場合の使い方useCallback
の依存配列を指定しない場合の使い方useCallback
を用いて再レンダリングを防ぐuseEffect
内でuseCallback
を用いてエフェクトが頻繁に発火するのを防ぐ
などをサンプルコードを用いて分かりやすく説明するように心掛けています。ご参考になれば幸いです。
useCallbackとは
useCallback
は、コールバック関数のメモ化を可能にするReactフックの一つです。
メモ化はキャッシュ化のことです。useCallback
の第2引数に渡す依存配列の要素が変化しない場合には、メモ化(キャッシュ化)したコールバック関数を返します。依存配列の要素のいずれかが変化した場合には、コールバック関数を再生成します。
補足
useCallback
はReact16.8で導入されたフックです。useCallback
を使用することで、無駄な再レンダリングを防ぐことができます。
useCallbackの構文
useCallback
の構文を以下に示します。useCallback
は2つの引数を取ります。第1引数はメモ化したいコールバック関数、第2引数は依存配列です。依存配列に含まれる値が変化した場合のみ、コールバック関数が再生成されます。
useCallbackの構文
const cachedFn = useCallback(callbackFn, [dependencies])
callbackFn
- メモ化したいコールバック関数
dependencies
- 依存配列
useCallback
を使用しない場合と使用した場合のコードを比較してみましょう。
useCallbackを使用しない場合のコード
const noCachedFn = () => {
sampleFn(a, b)
}
上記のコードは一般的なアロー関数です。このnoCachedFn
は、レンダリングされる度に新しく生成されます。
useCallbackを使用した場合のコード
const cachedFn = useCallback(() => {
sampleFn(a, b)
}, [a, b]);
上記に示すようにusecallback
を使うと、依存配列の要素a
,b
のいずれかが変化した場合のみ、cachedFn
を再生成します。一方、依存配列の要素a
,b
が変化しなければ、メモ化したcachedFn
を再利用するため、レンダリングされる度にcachedFn
を新しく生成しません。
useCallbackの使い方
useCallback
について、以下に示している使い方をこれから説明します。
useCallback
の依存配列を指定する場合の使い方useCallback
の依存配列を指定しない場合の使い方useCallback
を用いて再レンダリングを防ぐuseEffect
内でuseCallback
を用いてエフェクトが頻繁に発火するのを防ぐ
useCallbackの依存配列を指定する場合の使い方
useCallback
の第2引数に依存配列を指定した場合のサンプルコードを以下に示しています。
import React, { useState, useCallback } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + step);
}, [step]); // stepが依存配列に含まれている
return (
<>
<p>Count: {count}</p>
<input type="number" value={step} onChange={(e) => setStep(Number(e.target.value))} />
<button onClick={increment}>Increment</button>
</>
);
};
export default App;
上記のサンプルコードでは、increment
関数がuseCallback
を使用してメモ化されています。useCallback
の依存配列にstep
が含まれているため、step
が変更されるたびにincrement
関数が再生成されます。これにより、increment
関数内で常に最新のstep
の値が使用されます。
ポイント
step
の値が変更されるたびにincrement
関数が再生成されるため、最新のstep
の値を使用することができる。useCallback
を正しく使用することで、関数の再生成を制御できる(step
が変更されないとincrement
関数が再生成されない)。
useCallbackの依存配列を指定しない場合の使い方
useCallback
の第2引数に依存配列を指定しない場合のサンプルコードを以下に示しています。
import React, { useState, useCallback } from 'react';
const App = () => {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1); // stepの初期値は1
// increment関数をuseCallbackを使用してメモ化している
const increment = useCallback(() => {
setCount((prevCount) => prevCount + step);
}, []); // 依存配列が空
return (
<>
<p>Count: {count}</p>
<input type="number" value={step} onChange={(e) => setStep(Number(e.target.value))} />
<button onClick={increment}>Increment</button>
</>
);
};
export default App;
上記のサンプルコードでも、increment
関数はuseCallback
を使用してメモ化されています。しかし、useCallback
の依存配列が空のため、step
の値が変更されてもincrement
関数は再生成されません。その結果、incrementCount
内で使用されるstep
の値は最新のものになりません。初期値の1
の値のままになります。
テキストボックスの数値を変えると、step
の値は変更されますが、incrementCount
は再生成されず、メモ化したものを使用するため、incrementCount
内で使用されるstep
の値は初期値の1
のままということです。
ポイント
useCallback
の依存配列が空のため、increment
関数は初回レンダリング時に一度だけ生成され、それ以降は再生成されない。step
の値を変更しても、increment
関数は再生成されないため、最新のstep
の値を反映することができない。
useCallbackを用いて再レンダリングを防ぐ
次に、useCallback
を使用して無駄な再レンダリングを防ぐ例を示します。
import React, { useState, useCallback } from 'react';
const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Increment</button>;
});
const App = () => {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1); // stepの初期値は1
const [otherState, setOtherState] = useState(false);
// increment関数をuseCallbackを使用してメモ化している
const increment = useCallback(() => {
setCount((prevCount) => prevCount + step);
}, [step]); // stepが依存配列に含まれている
return (
<>
<p>Count: {count}</p>
<input type="number" value={step} onChange={(e) => setStep(Number(e.target.value))} />
<ChildComponent onClick={increment} />
<button onClick={() => setOtherState(!otherState)}>Toggle Other State</button>
</>
);
};
export default App;
上記のサンプルコードでは、ChildComponent
は親コンポーネントからuseCallback
を使用してメモ化されたincrement
関数を受け取ります。App
コンポーネントが再レンダリングされた際に、メモ化したincrement
関数が再生成されていなければ、ChildComponent
は再レンダリングされません。
テキストボックスの数値を変えて、step
の値を変更した場合には、increment
関数が再生成されるので、ChildComponent
は再レンダリングされ、ChildComponent rendered
とログ出力されます。
ボタンをクリックして、App
コンポーネントの他の状態(otherState
)を変更しても、メモ化したincrement
関数が再生成されないため(依存配列にotherState
を用いていないため再生成されない)、ChildComponent
は再レンダリングされません。
ポイント
useCallback
はReact.memo
と組み合わせて使うことでパフォーマンス最適化がしやすくなります。- 上記のサンプルコードでは、
React.memo
を使用してChildComponent
の再レンダリングを最適化しています。
- 上記のサンプルコードでは、
React.memoとは
React.memo
は、関数コンポーネントのメモ化を可能にするReactフックの一つです。親コンポーネントから同じプロップスを渡された場合、再レンダリングされることを防ぎます。
useCallbackを用いないことで再レンダリングされてしまう例
次に、useCallback
を用いないことで再レンダリングが発生してしまう例を示します。
import React, { useState, useCallback } from 'react';
const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Increment</button>;
});
const App = () => {
const [count, setCount] = useState(0);
const [step, setStep] = useState(1); // stepの初期値は1
const [otherState, setOtherState] = useState(false);
// increment関数をメモ化していない場合
const increment = () => {
setCount((prevCount) => prevCount + 1);
};
return (
<>
<p>Count: {count}</p>
<input type="number" value={step} onChange={(e) => setStep(Number(e.target.value))} />
<ChildComponent onClick={increment} />
<button onClick={() => setOtherState(!otherState)}>Toggle Other State</button>
</>
);
};
export default App;
上記のサンプルコードでは、ChildComponent
は親コンポーネントからメモ化されていないincrement
関数を受け取っています。そのため、App
コンポーネントが再レンダリングされた場合、increment
関数も再生成され、その結果、ChildComponent
もレンダリングされてしまいます。
useEffect内でuseCallbackを用いてエフェクトが頻繁に発火するのを防ぐ
次に、useCallback
を使用して、useEffect
内で実行する関数をメモ化する例を示します。これにより、レンダリングの度にエフェクトが発火するのを防ぐことができます。
import React, { useState, useEffect, useCallback } from 'react';
const App = () => {
const [message, setMessage] = useState('');
const [otherState, setOtherState] = useState(false);
const outputLog = useCallback((value) => {
console.log(value);
},[message]);
// Appコンポーネントの他の状態(otherState)を変更しても、Effectが発火しない
useEffect(() => {
outputLog(message);
}, [outputLog]);
return (
<>
<input type="text" value={message} onChange={(e) => setMessage(e.target.value)} />
<button onClick={() => setOtherState(!otherState)}>Toggle Other State</button>
</>
);
};
export default App;
上記のサンプルコードでは、outputLog
関数をuseCallback
でメモ化し、それをuseEffect
内で使用しています。これにより、outputLog
関数は再生成されない場合、Effect
の発火を防ぐことができます。
テキストボックスに入力する文字を変え、message
の値を変更した場合には、outputLog
関数が再生成されるので、Effect
が発火します。
一方、ボタンをクリックして、App
コンポーネントの他の状態(otherState
)を変更しても、メモ化したoutputLog
関数が再生成されないため(依存配列にotherState
を用いていないため再生成されない)、Effect
は発火しません。
本記事のまとめ
この記事ではReactフックの1つである『useCallback』について、以下の内容を説明しました。
useCallback
とはuseCallback
の構文useCallback
の使い方useCallback
の依存配列を指定する場合の使い方useCallback
の依存配列を指定しない場合の使い方useCallback
を用いて再レンダリングを防ぐuseEffect
内でuseCallback
を用いてエフェクトが頻繁に発火するのを防ぐ
お読み頂きありがとうございました。