0Pricing
Frontend Academy · レッスン

リアルタイム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フィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. WebSocket API:open・message・close・error
  2. Socket.ioクライアントの統合
  3. 一方向ストリーミングのためのServer-Sent Events
  4. リアルタイムUIのパターン
← Frontend Academyに戻る