React.jsのフックの一つである useCallback は、関数の再生成を防ぎ、パフォーマンスを向上させるための便利な機能です。
本記事では useCallback の基本的な使い方から実践的なサンプル、ベストプラクティスや注意点について詳しく解説します。
目次
useCallbackとは
useCallback は、特定の依存関係が変化しない限り、同じ関数を保持するためのReactフックです。
これにより、再レンダリングの際に関数が無駄に再生成されるのを防ぎ、アプリケーションのパフォーマンスを向上させることができます。
基本的な使い方
useCallbackを使用する際に関数が再生成されるか否かは、依存関係配列で決まります。
依存関係配列を設定していない「空」の場合は、コンポーネントが初めてマウントされたタイミングでのみ関数が生成され、その後は再生成されません。
依存関係を設定している場合は、指定された依存関係が変更されるたびに関数が再生成されます。動的なデータに基づいて関数の動作を変更する必要がある場合に使用します。
useCallback の基本的な使い方は以下の通りです。
依存関係配列を設定していない場合の例
import React, { useCallback } from 'react';
const MyComponent = () => {
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return <button onClick={handleClick}>Click me</button>;
};
この例では、ボタンをクリックすると handleClick 関数が呼び出されます。useCallback フックを使用することで、この関数(緑、太字部分)は再レンダリングが発生しても再生成されません。
依存関係配列を設定している場合の例
const handleClick = useCallback(() => {
console.log('Button clicked', count);
}, [count]);
この場合、count が依存関係配列に設定されているため、count が変更されるたびに新しい関数(緑、太字部分の無名関数)が生成されます。count が変更されなければ、関数は再生成されません。
よくある使い方と実践的なサンプル
大規模なリストの項目のフィルタリングとメモ化
import React, { useState, useCallback, useMemo } from 'react';
const filterItems = (items, query) => {
console.log('Filtering items');
return items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
};
const MyComponent = ({ items }) => {
const [query, setQuery] = useState('');
const filteredItems = useMemo(() => filterItems(items, query), [items, query]);
const handleChange = useCallback((e) => {
setQuery(e.target.value);
}, []);
return (
<div>
<input type="text" value={query} onChange={handleChange} />
<ul>
{filteredItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
};
このプログラムでは、大規模なリストの項目をフィルタリングするために useMemo と useCallback を組み合わせて使用しています。フィルタリング処理を useMemo でメモ化することで、items または query が変更されたときのみフィルタリングが行われます。これにより、無駄なフィルタリング処理を避け、パフォーマンスを向上させます。
また、handleChange 関数は useCallback でメモ化され、入力フィールドの変更が発生したときのみ関数が再生成されます。
このように、useCallback は関数そのものの再生成は防止できるものの、関数の実行結果を保持するわけではなく、再呼び出しを防ぐわけでもないので、useMemo や、後述の React.memo などと組み合わせて使用するケースが多々あります。
複数の子コンポーネントに関数を渡す場合の最適化
import React, { useState, useCallback } from 'react';
const ChildComponent = React.memo(({ onAction }) => {
console.log('ChildComponent rendered');
return <button onClick={onAction}>Action</button>;
});
const ParentComponent = () => {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
return (
<div>
<ChildComponent onAction={increment} />
<ChildComponent onAction={increment} />
<div>Count: {count}</div>
</div>
);
};
このプログラムでは、ParentComponent が複数の ChildComponent に increment 関数をpropsとして渡しています。ChildComponent は React.memo(※1) でメモ化されており、propsが変更されない限り再レンダリングされません。
ParentComponent では、useState フックを使用して count という状態変数を定義し、useCallback フックを使用して increment というコールバック関数を作成しています。この関数は、依存関係配列が空なので再生成されず、不要な再レンダリングを防ぎます。
ベストプラクティスと注意点
ベストプラクティス
- 必要な場合のみ使用:パフォーマンス最適化が必要な場合に限り、
useCallbackを使用しましょう。特に、関数が頻繁に再生成されることでパフォーマンスに悪影響を及ぼす可能性がある場合に有効です。 - 依存関係の適切な設定:依存関係の配列を正確に設定することで、意図しない関数の再生成を防ぎます。特に、子コンポーネントにプロパティとして関数を渡す場合は重要です。
- 他の最適化手法との組み合わせ:
useMemoやReact.memoと組み合わせて使用することで、計算結果のメモ化や不要な再レンダリングを防ぎ、全体的なパフォーマンスを向上させます。
注意点
- 過剰な使用を避ける:
useCallbackの過剰な使用は、コードの可読性やメンテナンス性を低下させることがあります。軽量な関数や頻繁に再生成される必要がない関数には使用しないことが推奨されます。 - 依存関係の見落とし:依存関係を正確に指定しないと、意図しない動作やパフォーマンスの問題が発生する可能性があります。依存関係が設定されていない場合でも、場合によっては有用なことがあります。
- 関数の結果を保持するわけではない:
useCallbackは関数の再生成を防ぐためのものであり、関数の実行結果を保持するわけではありません。関数の実行結果をメモ化したい場合は、useMemoなどの他の最適化手法と組み合わせて使用することが重要です。
まとめ
本記事では、useCallback フックの基本的な使い方から実践的なサンプル、ベストプラクティスや注意点について解説しました。useCallback を適切に活用することで、アプリケーションのパフォーマンスを向上させることができますが、具体的な要件に応じて他の最適化手法と組み合わせて使用することが重要です。
本記事についての質問、誤りの指摘、ご意見ご感想などありましたら、ぜひコメント頂ければ幸いです。
それでは、最後までお読みいただき、ありがとうございます。


コメント