Frontend Academy · บทเรียน

รูปแบบ UI แบบเรียลไทม์

ใช้การอัปเดตเชิงคาดการณ์เพื่อให้ผู้ใช้ได้รับการตอบสนองทันที จัดคิวในเครื่องเมื่อออฟไลน์ และเปรียบเทียบข้อมูลที่เข้ามาเพื่ออัปเดตเฉพาะรายการที่เปลี่ยนแปลงใน UI

บทเรียน 4 จาก 414 ขั้นตอน

รูปแบบ UI แบบเรียลไทม์ เป็นบทเรียน Frontend Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Frontend Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Frontend Academy มีบทเรียนทั้งหมด 4 บทเรียน

เรียลไทม์ที่ให้ประสบการณ์เหมือนเรียลไทม์

เรียลไทม์ไม่ได้หมายถึงความเร็วเพียงอย่างเดียว แต่ยังต้องตอบสนองได้ คาดการณ์ได้ และรองรับความหน่วงได้อย่างเหมาะสม รูปแบบหลักสามอย่างคือ การอัปเดตเชิงคาดหวัง การจัดคิวในเครื่อง และการเปรียบเทียบความแตกต่างอย่างชาญฉลาด

การอัปเดตเชิงคาดหวัง

อัปเดตส่วนติดต่อผู้ใช้ทันทีเมื่อผู้ใช้ดำเนินการ โดยไม่รอการยืนยันจากเซิร์ฟเวอร์ หากเซิร์ฟเวอร์ปฏิเสธ ให้ย้อนกลับการเปลี่ยนแปลง วิธีนี้ทำให้แอปตอบสนองเหมือนทันที

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
    ));
  }
});

การจำกัดความถี่ของการอัปเดตจำนวนมาก

สตรีมบางชนิดส่งการอัปเดตหลายครั้งต่อวินาที เช่น ตำแหน่งเมาส์และราคาหุ้น ให้จำกัดการอัปเดตส่วนติดต่อผู้ใช้ไว้ที่ประมาณ 60 Hz เพื่อไม่ให้เบราว์เซอร์กระตุก

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>
)}

การแก้ไขข้อขัดแย้ง

แอปแบบเรียลไทม์ต้องรองรับการแก้ไขพร้อมกันที่ขัดแย้งกัน กลยุทธ์ที่ใช้ได้มีดังนี้: การให้การเขียนครั้งล่าสุดชนะ ซึ่งเรียบง่ายแต่ทำให้ข้อมูลบางส่วนสูญหาย เวกเตอร์รุ่น OT (การแปลงการดำเนินการ) และ CRDTs (Yjs, Automerge)

CRDTs สำหรับการทำงานร่วมกัน

CRDTs (ชนิดข้อมูลจำลองแบบไร้ข้อขัดแย้ง) ช่วยให้แก้ไขข้อมูลร่วมกันแบบเรียลไทม์ได้โดยไม่ต้องมีเซิร์ฟเวอร์ส่วนกลาง ไลบรารีที่ใช้ได้ ได้แก่ Yjs และ Automerge พบการใช้งานในแอปสไตล์ Figma, Linear และ Notion

ประสิทธิภาพ: รายการแบบจำลองเสมือน

ฟีดแบบเรียลไทม์อาจยาวขึ้นเรื่อย ๆ ให้ใช้รายการแบบจำลองเสมือน เช่น react-virtuoso และ @tanstack/react-virtual เพื่อแสดงเฉพาะรายการที่มองเห็นได้ การใช้หน่วยความจำและ CPU จึงคงที่ไม่ว่าจำนวนรายการทั้งหมดจะเท่าใด

ตรวจสอบความเข้าใจ

การอัปเดตเชิงคาดหวังในส่วนติดต่อผู้ใช้แบบเรียลไทม์คืออะไร

สรุป: รูปแบบส่วนติดต่อผู้ใช้แบบเรียลไทม์

การอัปเดตเชิงคาดหวังทำให้แอปตอบสนองเหมือนทันที และให้ย้อนกลับเมื่อเกิดข้อผิดพลาด ใช้ ID ชั่วคราวสำหรับรายการที่สร้างขึ้น จัดคิวในเครื่องเพื่อให้ทำงานต่อได้เมื่อออฟไลน์ เปรียบเทียบหรือใช้แพตช์กับการอัปเดตจากเซิร์ฟเวอร์เพื่อหลีกเลี่ยงการวาดใหม่ จำกัดความถี่ของสตรีมที่มีการอัปเดตสูง แสดงสถานะการมีอยู่ เช่น ออนไลน์และกำลังพิมพ์ รวมถึงแบนเนอร์สถานะการเชื่อมต่อ ใช้ CRDTs (Yjs, Automerge) สำหรับการแก้ไขร่วมกัน และใช้รายการแบบจำลองเสมือนกับรายการเรียลไทม์ที่ยาว

เริ่มต้นได้ฟรี

เรียนรู้ HTML ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
41
บทเรียน
163

คำถามที่พบบ่อย

บทเรียน “รูปแบบ UI แบบเรียลไทม์” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “รูปแบบ UI แบบเรียลไทม์” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Frontend Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Frontend Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “รูปแบบ UI แบบเรียลไทม์”

ใช้การอัปเดตเชิงคาดการณ์เพื่อให้ผู้ใช้ได้รับการตอบสนองทันที จัดคิวในเครื่องเมื่อออฟไลน์ และเปรียบเทียบข้อมูลที่เข้ามาเพื่ออัปเดตเฉพาะรายการที่เปลี่ยนแปลงใน UI คุณปฏิบัติ Frontend Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Frontend Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Frontend Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “รูปแบบ UI แบบเรียลไทม์” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Frontend Academy นี้ได้ไหม

ได้ บทเรียน Frontend Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. WebSocket API: เปิด รับข้อความ ปิด และข้อผิดพลาด
  2. การเชื่อมต่อ Socket.io ฝั่งไคลเอนต์
  3. เหตุการณ์จากเซิร์ฟเวอร์สำหรับการสตรีมทางเดียว
  4. รูปแบบ UI แบบเรียลไทม์
← กลับไปที่ Frontend Academy