楽観的UI更新について
楽観的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