👏

楽観的UI更新について

2024/10/09に公開

楽観的UIとは?

楽観的UIとは、ユーザーの操作が完了する前に、その結果を即座に画面に反映させる技術です。通常、データがサーバーと同期されるまでの待ち時間を短縮し、ユーザー体験を向上させることが目的です。操作後にすぐフィードバックを得られるため、特にレスポンスの遅いAPIや不安定なネットワーク下でも快適なユーザー体験を提供できます。

Reactで楽観的UIを実装する際、以下の流れで進めます。

  • ローカル状態を即座に更新
  • APIリクエストを送信
  • レスポンスに基づき最終的な状態を確定

では、具体的にどのように楽観的UIを実装するのか、シンプルな例で見ていきましょう。

例:タスク管理アプリにおける楽観的UI

前提

サーバーからタスクを取得し、追加・更新・削除を行うシンプルなタスク管理アプリを例にします。

1. タスクの追加機能

まず、タスクを追加するUIを考えます。通常のタスク追加処理では、サーバーにリクエストを送信し、そのレスポンスを待って画面を更新しますが、楽観的UIではそのリクエストの前に画面を更新します。

import { useState } from 'react';
import { supabase } from './supabaseClient';
import { Task } from './types';

type Task = {
  id: number;
  title: string;
  status: 'pending' | 'complete';
};

function TaskList() {
  const [tasks, setTasks] = useState<Task[]>([]);

  const addTask = async (title: string) => {
    // 1. 楽観的にローカル状態を更新
    const newTask: Task = { id: Date.now(), title, status: 'pending' };
    setTasks([...tasks, newTask]);

    try {
      // 2. サーバーにデータを送信
      const { data, error } = await supabase.from('tasks').insert([{ title }]);
      if (error) throw error;

      // 3. サーバーからのレスポンスに基づいて状態を更新
      setTasks((prevTasks) =>
        prevTasks.map((task) =>
          task.id === newTask.id ? { ...task, id: data[0].id, status: 'complete' } : task
        )
      );
    } catch (error) {
      setTasks((prevTasks) => prevTasks.filter((task) => task.id !== newTask.id));
      console.error('Error adding task:', error);
    }
  };

  return (
    <div>
      <h1>Task List</h1>
      <button onClick={() => addTask('New Task')}>Add Task</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.title} ({task.status})</li>
        ))}
      </ul>
    </div>
  );
}

export default TaskList;
実装のポイント

楽観的な状態更新
APIリクエストを行う前に、ローカル状態に新しいタスクを即座に追加します。この段階では、サーバーのレスポンスを待たず、あくまで「楽観的」にUIを更新します。

APIリクエストの処理
Supabaseを使ってサーバーに新しいタスクを送信します。サーバーからのレスポンスが返ってきた際に、タスクIDやステータスを更新して、最終的なデータをローカル状態に反映させます。

エラーハンドリング
万が一サーバー側でエラーが発生した場合、ローカルで追加したタスクを削除して、楽観的な更新を取り消します。これにより、ユーザーに不正なデータが表示され続けることを防ぎます。

2. 削除機能

同様に、タスクを削除する際にも楽観的UIを適用できます。

const deleteTask = async (taskId: number) => {
  // 1. ローカル状態から即座にタスクを削除
  const previousTasks = tasks;
  setTasks(tasks.filter((task) => task.id !== taskId));

  try {
    // 2. サーバーに削除リクエストを送信
    const { error } = await supabase.from('tasks').delete().eq('id', taskId);
    if (error) throw error;
  } catch (error) {
    // エラーハンドリング(削除失敗時は元の状態に戻す)
    setTasks(previousTasks);
    console.error('Error deleting task:', error);
  }
};

楽観的UIのメリット

レスポンス速度の向上: APIリクエストの処理時間を待たずに、即座にUIが更新されるため、ユーザーの操作感が向上します。
ユーザーエクスペリエンスの向上: サーバーとの通信遅延を感じさせないスムーズな体験を提供できます。

楽観的UIのデメリット

サーバーとの不整合の可能性: サーバーとの通信が失敗した場合、表示されている内容が実際のデータと一致しなくなる可能性があります。このため、エラーハンドリングやロールバック処理が重要です。

まとめ

Reactで楽観的UIを実装することで、ユーザー体験を大幅に向上させることができます。特に、APIのレスポンスが遅い場合や、リアルタイム性が求められるアプリケーションでは有効です。

Discussion