リアルタイムUIのパターン
即時フィードバックのために楽観的更新を適用し、オフライン時はローカルキューを使い、受信データとの差分を取り、UIでは変更された項目だけを更新します。
「リアルタイムUIのパターン」はCoddyKit上の無料Frontend Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはFrontend Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Frontend Academyコースには全4レッスンが含まれています。
リアルタイムとは体感のよいUX
リアルタイムとは、単に速いだけではありません。応答性が高く、予測しやすく、遅延にも柔軟に対応できることです。中心となるパターンは、楽観的更新、ローカルキュー、スマートな差分計算の3つです。
楽観的更新
サーバーの確認を待たず、ユーザーの操作直後にUIを更新します。サーバーに拒否された場合は元に戻します。アプリを瞬時に反応するように感じさせることができます。
async function likePost(id) {
// 1. Optimistically update UI:
setLikes(l => ({ ...l, [id]: (l[id] || 0) + 1 }));
setLiked(prev => ({ ...prev, [id]: true }));
try {
await fetch(`/api/posts/${id}/like`, { method: 'POST' });
} catch (err) {
// 2. Rollback on failure:
setLikes(l => ({ ...l, [id]: (l[id] || 1) - 1 }));
setLiked(prev => ({ ...prev, [id]: false }));
toast.error('Could not like — please retry');
}
}一時IDを使った楽観的更新
作成するアイテムには、ローカルで一時IDを割り当てます。レスポンスが届いたら、サーバーの実際のIDに置き換えます。
async function addComment(text) {
const tempId = `tmp-${Date.now()}`;
setComments(c => [...c, { id: tempId, text, pending: true }]);
try {
const saved = await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify({ text })
}).then(r => r.json());
setComments(c => c.map(m => m.id === tempId ? saved : m));
} catch (err) {
setComments(c => c.map(m => m.id === tempId ? { ...m, error: true } : m));
}
}オフライン時のローカルキュー
ネットワークが停止している間は操作をローカルにキューイングし、復旧したら再実行します。ユーザーは作業を続けられます。
const queue = JSON.parse(localStorage.getItem('queue') || '[]');
function enqueue(action) {
queue.push(action);
localStorage.setItem('queue', JSON.stringify(queue));
trySend();
}
async function trySend() {
while (queue.length && navigator.onLine) {
const action = queue[0];
try {
await fetch(action.url, action);
queue.shift();
localStorage.setItem('queue', JSON.stringify(queue));
} catch {
break; // stop until online
}
}
}
window.addEventListener('online', trySend);受信したリアルタイムデータの差分計算
サーバーがリスト全体をプッシュする場合は、現在の状態との差分を計算し、変更された部分だけを更新します。不要な再レンダリングやアニメーションの不具合を防げます。
function applyServerSnapshot(newList, currentList) {
const current = new Map(currentList.map(i => [i.id, i]));
const result = newList.map(item => {
const prev = current.get(item.id);
if (!prev) return { ...item, _state: 'new' };
if (prev.updatedAt !== item.updatedAt) return { ...item, _state: 'updated' };
return prev;
});
return result;
}パッチベースの更新
完全なスナップショットを送るよりも、サーバーが変更されたフィールドだけを送る方法が優れています。段階的に適用できるため、帯域幅を節約し、より速く描画できます。
// Server sends: { type: 'patch', id: 42, fields: { likes: 11 } }
ws.on('message', (msg) => {
if (msg.type === 'patch') {
setPosts(posts => posts.map(p =>
p.id === msg.id ? { ...p, ...msg.fields } : p
));
}
});高頻度更新のスロットリング
一部のストリームでは、1秒あたりに多数の更新が送られます(マウス位置、株価のティックなど)。ブラウザの表示がカクつかないよう、UIの更新を約60Hzにスロットリングしてください。
import { throttle } from 'lodash-es';
const applyUpdate = throttle((data) => {
setData(data);
}, 16); // ~60fps
ws.on('tick', applyUpdate);プレゼンスインジケーター
オンライン中または入力中のユーザーをリアルタイムで表示します。サーバーがプレゼンスイベントを発行し、クライアントがアバター付きのリストを描画します。
ws.on('presence', ({ users }) => {
setOnlineUsers(users);
});
ws.on('typing', ({ user, isTyping }) => {
setTypingUsers(t => isTyping
? [...t.filter(u => u.id !== user.id), user]
: t.filter(u => u.id !== user.id));
});接続状態バナー
オフラインまたは再接続中のときに、控えめなバナーを表示します。完全に再接続できたら非表示にします。状況が見えると、ユーザーは遅延をはるかに受け入れやすくなります。
{!isConnected && (
<div className="connection-banner">
Reconnecting... Your changes will sync when you're back online.
</div>
)}競合の解決
リアルタイムアプリでは、同時に行われる編集の競合を処理する必要があります。戦略には、last-write-wins(シンプルですが情報が失われます)、バージョンベクトル、OT(operational transformation)、CRDT(Yjs、Automerge)があります。
共同編集のためのCRDT
CRDT(Conflict-free Replicated Data Types)を使うと、中央サーバーなしでリアルタイムの共同編集を実現できます。ライブラリにはYjsやAutomergeがあります。Figma、Linear、Notionのようなアプリで使用されています。
パフォーマンス:仮想化リスト
リアルタイムフィードは長くなることがあります。仮想化リスト(react-virtuoso、@tanstack/react-virtual)を使うと、表示中のアイテムだけを描画できます。総アイテム数に関係なく、メモリとCPUの使用量を一定に保てます。
確認問題
リアルタイムUIにおける「楽観的更新」とは何ですか?
まとめ:リアルタイムUIのパターン
楽観的更新でアプリを瞬時に反応するように感じさせ、失敗時には元に戻します。作成するアイテムには一時IDを使用します。オフライン時の永続性にはローカルキューを使用します。サーバーからの更新には差分またはパッチを使い、再レンダリングを防ぎます。高頻度のストリームはスロットリングします。プレゼンス(オンライン、入力中)と接続状態バナーを表示します。共同編集にはCRDT(Yjs、Automerge)を使用します。長いリアルタイムリストは仮想化します。
よくある質問
「リアルタイムUIのパターン」レッスンは無料ですか?
はい。「リアルタイムUIのパターン」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Frontend Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Frontend Academyコースには全4レッスンが含まれています。
「リアルタイムUIのパターン」で何を学びますか?
即時フィードバックのために楽観的更新を適用し、オフライン時はローカルキューを使い、受信データとの差分を取り、UIでは変更された項目だけを更新します。 ブラウザで直接実行するハンズオンコードでFrontend Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Frontend Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのFrontend Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「リアルタイムUIのパターン」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このFrontend Academyレッスンでコードを書いて実行できますか?
はい。すべてのFrontend Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- WebSocket API:open・message・close・error
- Socket.ioクライアントの統合
- 一方向ストリーミングのためのServer-Sent Events
- リアルタイムUIのパターン