突然ですが質問です。
質問:
ユーザがサーチバーに入力するたびにAPIを呼び出しています。
これではサーバに過剰な負荷がかかります。
どう最適化すればよいでしょうか?
こんな質問をされたらどう答えますか?
会話の流れ
面接官:
「サーチバーでユーザが入力するたびにAPIが呼ばれています。
これがサーバに負荷をかけています。どうすればよいでしょう?」
エンジニア:
「たしかに、毎回リクエストが飛ぶと効率が悪いですね。」
「**Debouncing(デバウンス)**を使うと良いです。
これは“入力が止まってから一定時間(例:500ms)経過したら関数を実行する”方法です。
たとえば商品検索でも、タイプ中はAPIを呼ばず、止まってから1回だけ検索を実行します。」
面接官:
「なるほど! サーバも軽くなるし、動作も安定しそうですね。」
Debouncingとは?
| 状況 | 挙動 | 特徴 |
|---|---|---|
| デバウンスなし | 入力のたびにAPI呼び出し | サーバ負荷が高い |
| デバウンスあり | 入力が止まってから1回だけ呼び出し | 無駄がなく効率的 |
React実装例(実際に動く)
使用API:JSONPlaceholder
エンドポイント: /users
このAPIは「ユーザー情報(名前やメールなど)」を返します。
codeSandBoxにコピペで試せるので是非
react環境を構築し、App.jsxに今記事のコードをコピペするだけで手軽に試せます
デバウンスなしの検索
import React, { useState, useEffect } from "react";
// デバウンスなしバージョン
const App = () => {
const [query, setQuery] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) {
setResults([]);
return;
}
const fetchUsers = async () => {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
const filtered = data.filter((user) =>
user.name.toLowerCase().includes(query.toLowerCase())
);
setResults(filtered);
} catch (err) {
console.error("API error:", err);
}
};
fetchUsers();
}, [query]);
return (
<div className="p-4 border rounded w-full">
<h2 className="font-bold text-lg mb-2">デバウンスなし</h2>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="ユーザー名を入力"
className="border p-2 w-full"
/>
<ul className="mt-2">
{results.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
export default App;
実行結果
各入力のたびに(即座に)検索結果が表示されていることがわかりますね
デバウンスありの検索(最適化)
import { useState, useEffect } from "react";
export const App = () => {
const [query, setQuery] = useState("");
const [debouncedQuery, setDebouncedQuery] = useState("");
const [results, setResults] = useState([]);
// 入力が止まってから500ms後に反映
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedQuery(query);
}, 500);
return () => clearTimeout(timer);
}, [query]);
useEffect(() => {
if (!debouncedQuery) {
setResults([]);
return;
}
const fetchUsers = async () => {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
const filtered = data.filter((user) =>
user.name.toLowerCase().includes(debouncedQuery.toLowerCase())
);
setResults(filtered);
} catch (err) {
console.error("API error:", err);
}
};
fetchUsers();
}, [debouncedQuery]);
return (
<div className="p-6">
<h2 className="font-bold text-lg mb-2">デバウンスあり(500ms遅延)</h2>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="ユーザー名を入力"
className="border p-2 w-full"
/>
<ul className="mt-4">
{results.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
};
実行結果
入力が完了されてから検索結果が表示されているのがわかりますね
| 比較項目 | デバウンスなし | デバウンスあり |
|---|---|---|
| API呼び出し頻度 | 文字入力のたび | 入力停止後のみ |
| サーバ負荷 | 高い | 低い |
| 体感UX | カクつく | なめらか |
※ 一長一短ではなく、ケースバイケースではあります。
目的によっては毎回のfetchがあってもいいかもしれません。
常に何がこの場における最適化か?を自問自答するようにいただければと思います。
まとめ
-
デバウンスを導入するだけでAPIの呼び出し頻度を劇的に削減できる
-
入力完了を待つことで、ユーザ体験も向上する
-
実務ではさらに
-
AbortControllerで古いリクエストをキャンセル -
React QueryやSWRでキャッシュ最適化
などを組み合わせるとさらにgood
-

