【入門編】 useDeferredValueによる副作用の遅延実行 – React実践ガイド

こんにちは!Reactの海へようこそ。チーフアーキテクトの私です。

日々のフロントエンド開発、本当にお疲れ様です!
「画面のボタンを押した瞬間、アプリがカクッとフリーズした…」「文字を入力するたびに、なんだか重たい…」
そんな経験、ありませんか?

Reactを学び始めの頃は、画面にデータを反映させるだけで精一杯ですよね。でも、アプリが少しずつ大きくなってくると、避けて通れないのが「パフォーマンス(重さ)の壁」です。

今回は、そんな重たい処理に立ち向かうための心強い相棒、`useDeferredValue` について、身近なたとえ話を交えながら優しく紐解いていきたいと思います。難しく考えなくて大丈夫ですよ。一歩ずつ、一緒に見ていきましょう!

—

1. なぜ文字入力や画面の切り替えが重くなるの?

まずは、何が起きているのかをイメージしてみましょう。

例えば、大きなお買い物をしているところを想像してください。
あなたは今、スーパーのレジに並んでいます。レジの店員さんが、ものすごいスピードで商品のバーコードを「ピッ!ピッ!」と読み取っています。この「バーコードを読み取る動作」が、ユーザーによる文字入力やクリックだと考えてみてください。

さて、ここで問題発生です。
レジの店員さんがバーコードを1回「ピッ!」と通すたびに、なぜか裏で「お店全体の在庫リストを全部印刷し直す」という激重な作業が挟まるとしたらどうでしょう?

「あ、すみません、もう一回バーコード読み取ってください…って、また印刷が始まっちゃったから動かない!」
これでは、レジの行列が進まなくなってしまいますよね。

Reactの世界でもこれと全く同じことが起きています。
私たちがキーボードで文字を「あ」「い」「う」と素早く入力しているのに、Reactが「文字が変わったから、この巨大なグラフやリストを今すぐ全部再計算して描き直さなきゃ!」と真面目に頑張りすぎてしまい、結果としてメインスレッド(作業台)がパンクし、画面がカクついてしまうのです。

—

2. 救世主 `useDeferredValue` の登場

ここで登場するのが、今回の主役である `useDeferredValue`(ユース・ディファード・バリュー) です。

名前をパッと見ると難しそうですが、英語の「Deferred(遅延された、後回しにされた)」という言葉のとおり、これは「重たい処理に使うデータを、ちょっとだけ後回し(お預け)にするための魔法のクッション」だと思ってください。

先ほどのスーパーの例でたとえるなら、`useDeferredValue` はこんな役割をしてくれます。

  • 「急ぎの用事(文字入力)」:お客さんのレジ打ち(最優先でサクサク処理する!)
  • 「後回しでもいい用事(重いリストの表示)」:在庫リストの印刷(レジが落ち着いてから、裏でこっそり進める)

`useDeferredValue` を使うと、Reactに対してこうお願いできます。
> 「ねえ、ユーザーが入力しているこの文字のデータ、今の今すぐ全部に反映させなくてもいいから、ちょっとだけ(数ミリ秒ほど)遅らせて渡してよ。まずはユーザーの入力画面をサクサク動かすのを最優先にして!」

これにより、「ユーザーの入力はサクサク動くのに、裏で重い処理もちゃんと動いている」という、理想的な滑らかさを手に入れることができるのです。

—

3. 実際のコードを見てみよう

百聞は一見にしかず。実際にコードを書いてその動きを実感してみましょう。
今回は、「文字を入力すると、大量のリストが絞り込まれて表示される」という、よくあるシーンを考えてみます。

以下のコードを、そのままあなたのReactアプリ(CodeSandboxやViteの環境など)に貼り付けて動かしてみてください。

import React, { useState, useId, useDeferredValue, useMemo } from ‘react’;

export default function App() {
const [text, setText] = useState(”);
const searchInputId = useId();

// ★ここがポイント!入力された値(text)の「遅延バージョン」を作るよ
const deferredText = useDeferredValue(text);

// 遅延された値(deferredText)を使って、あえて重い処理(大量のデータ作成)を行う
const listItems = useMemo(() => {
const items = [];
for (let i = 0; i < 15000; i++) { // 検索ワードが含まれているかチェック if (String(i).includes(deferredText)) { items.push(

  • アイテム番号: {i}
  • );
    }
    }
    return items;
    }, [deferredText]); // deferredTextが変わったときだけ、この重い計算が走る!

    return (

    useDeferredValue 体験デモ

    下の入力欄に、すばやく文字(例: 「1」など)を入力してみてください。

    入力自体はカクつかずにサクサク動くはずです!

    {/ ユーザーの入力は直結(text)しているので、文字打ちは一瞬で画面に反映される /}


    setText(e.target.value)}
    placeholder=”ここに入力…”
    style={{ padding: ‘8px’, fontSize: ’16px’, marginLeft: ’10px’ }}
    />

    {/ 現在の「急ぎの文字」と「遅延された文字」のステータスを表示 /}

    今の入力値(即時): {text}
    裏で処理中の値(遅延): {deferredText}

    {/ 重たいリストの表示 /}

    検索結果(全15,000件の中から一致するものを表示)

      {listItems}

    );
    }

    コードの解説と、つまずきやすいポイント

    1. 即時反映される `text` と、ワンテンポ遅れる `deferredText`
    入力欄(``)の `value` には、ユーザーが打った文字そのもの(`text`)をそのまま入れています。だから、キーボードを叩いた瞬間、入力欄の文字は1ミリの遅延もなく即座に表示されます。ここがポイントです!もしここを遅延させたら、タイピング中に文字がもたついてストレスになってしまいますからね。
    2. 重い処理の依存配列には `deferredText` を入れる
    15,000件のループを回す重たい処理(`useMemo`の中身)には、すぐには反映されない `deferredText` を渡しています。これにより、Reactは「あ、ユーザーはいま一生懸命文字を打ってるから、重いリストの再描画は少し後回しにしよう!」と賢く判断してくれます。

    —

    4. いつ使うべき?(実務での判断基準)

    「じゃあ、アプリ内のすべての変数に `useDeferredValue` をつければ万事解決だね!」……と言いたくなるところですが、それはちょっと待ってください。

    建築の世界でも、すべての柱に分厚い鉄板を入れたら、建物が重すぎて逆に沈んでしまいますよね。Reactも同じです。

    使うべきサイン

    • ユーザーの入力(文字入力、スライダーの動かしなど)と、それに連動する重い処理(大量データのフィルタリング、複雑なグラフの描画など)が同じ画面に同居しているとき。
    • 「入力がカクつく」というユーザーからの悲鳴が聞こえてきたとき。

    使わなくていい(使わない方がいい)場面

    • そもそもアプリが軽くて、何の遅延も起きていないとき(無駄な複雑さを持ち込まないのが、優れたエンジニアの鉄則です)。
    • ボタンのクリックやトタルの切り替えなど、「押した瞬間に即座に状態が切り替わったフィードバックを返さないといけないもの」(これに使うと、ボタンが反応しないように見えてユーザーが混乱します)。

    —

    さいごに

    ここまでお疲れ様でした!
    `useDeferredValue` のイメージ、少しずつ掴めてきたでしょうか?

    Reactのパフォーマンス最適化と聞くと、「なんだか難しそう…」「上級者のテクニックだ…」と身構えてしまうかもしれません。でも、本質はとてもシンプルです。

    「ユーザーが待たされてイライラするところを先回りして、裏で上手に順番待ちをさせてあげること」。

    今日覚えたこの優しさを、ぜひあなたの開発するアプリにもそっと取り入れてみてください。きっと、ユーザーから「このアプリ、なんだかサクサク動いて気持ちいいな!」と言ってもらえるはずです。

    あなたのReactライフが、もっと楽しく、もっと快適になりますように。
    それでは、また次の現場でお会いしましょう!チーフアーキテクトでした!

    コメント

    タイトルとURLをコピーしました