useDeferredValue

useDeferredValue で遅れてもよい表示用の値を分離する方法を学ぶ。

useDeferredValue は、値の更新を遅らせた版を返す Hook です。入力欄は現在の値をすぐ表示し、重い一覧だけを少し古い値で描画し続けられます。

表示側で遅延した値を使う

import { useDeferredValue, useState } from 'react';

function SearchPage({ items }: { items: string[] }) {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return <><input value={query} onChange={(event) => setQuery(event.target.value)} /><Results query={deferredQuery} items={items} dimmed={isStale} /></>;
}

この例では入力欄と Results の責務が分かれています。Results を memo 化している場合、deferred value の参照が変わるまで重い描画を避けられます。親の別の state 更新でも Results が重く描画されるなら、props の安定性も合わせて確認します。

query が変わっても、重い Results は React が余裕を持てるまで前の deferredQuery で描画されます。

遅延した値で行う描画は中断可能です。入力がさらに変わると、React は古い検索語の描画を捨てて新しい値でやり直せます。結果として入力欄は即座に反応し、一覧は少し遅れて追いつきます。

useTransition との違い

自分で state 更新を開始できるなら useTransition で更新を包めます。useDeferredValue は、親から受け取った値や変更できないコンポーネントの props を遅延させたいときに便利です。どちらもネットワーク要求を抑制する debounce ではありません。

落とし穴

遅延した値で表示する間、古い結果であることを見た目でも伝えます。たとえば opacity を下げたり更新中の表示を出したりします。オブジェクトをレンダーごとに新しく作ると常に新しい値と見なされるため、必要なら値の作り方を安定させます。

deferred value を使っても fetch の回数は自動では減りません。検索語を送信する通信を抑えたい場合は、キャッシュ、AbortController、debounce などを別に設計します。この Hook はあくまで React の描画優先度を扱うものです。

遅延を許せるかで決める

検索結果、集計グラフ、プレビューのように一瞬前の値でも意味が通じる UI に向いています。パスワード強度、在庫、価格、送信対象のように常に最新値でなければ誤解を招く情報には使いません。「古い表示を一瞬見せても利用者が正しく操作できるか」を判断基準にします。

まとめ

useDeferredValue は入力の即時性を守りながら、非重要な表示を後回しにします。遅れてよい表示と、即時に変える制御値を分けましょう。

Sources