React useDeferredValueでdebounceなしに検索を最適化する
問題
検索フィルター付きのリストを作りましたが、データが多いとキーストロークのたびに再レンダリングが走り、入力がカクつきます。debounceで対応はできますが、入力中に値が見えない固定の遅延が気になります。
function SearchList({ items }) {
const [query, setQuery] = useState('');
// itemsが10000件あると、毎回のタイピングで全件フィルタリング
const filtered = items.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase())
);
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<List items={filtered} />
</>
);
}
解決方法
useDeferredValueを使えば、入力は即座に反映しつつ、重いレンダリングは後回しにできます。
import { useState, useDeferredValue, memo } from 'react';
function SearchList({ items }) {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// 入力はqueryで即時反映、リストはdeferredQueryで遅延レンダリング
const filtered = items.filter(item =>
item.name.toLowerCase().includes(deferredQuery.toLowerCase())
);
const isStale = query !== deferredQuery;
return (
<>
<input value={query} onChange={e => setQuery(e.target.value)} />
<div style=>
<SlowList items={filtered} />
</div>
</>
);
}
// memoで囲まないと遅延効果が正しく動作しません
const SlowList = memo(function SlowList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
});
debounceとの違いはこうです:
debounce: 入力停止 → 300ms待機 → フィルタリング開始
useDeferredValue: 入力は即時反映 → ブラウザの空き時間にフィルタリング更新
ユーザーの入力は一切遅延せず、重い再レンダリングだけをReactが自動的に後回しにします。
ポイント
useDeferredValueは緊急な更新(入力)と非緊急な更新(リスト描画)を分離しますmemoと一緒に使う必要があります — 使わないと毎回再レンダリングされてしまいますdebounceと違い固定の遅延がないので、高速なデバイスではほぼ即時に反映され、低速なデバイスでのみ遅延が発生します