Kodokon kodokon.com

React สมัยใหม่: Server Components, use, useTransition, useOptimistic

สำรวจ server components, การอ่าน promise ด้วย use, และการอัปเดตแบบมองโลกในแง่ดี (optimistic updates) ของ React 19

11 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

React Server Components (RSC) วาดเส้นแบ่งระหว่างไคลเอนต์กับเซิร์ฟเวอร์ใหม่ server component รัน บนเซิร์ฟเวอร์เท่านั้น: โค้ดของมันไม่มีวันไปถึงบันเดิลฝั่งไคลเอนต์ มันสามารถเป็น async อ่านฐานข้อมูลหรือระบบไฟล์ได้โดยตรง และเอาต์พุตของมันถูกส่งไปยังเบราว์เซอร์ในรูปของ สตรีมที่ถูกซีเรียลไลซ์ (serialized stream) (RSC payload) ที่ React ผสานเข้ากับต้นไม้ฝั่งไคลเอนต์ ในทางกลับกัน มัน ไม่มีสเตต ไม่มีเอฟเฟกต์ และไม่สามารถเข้าถึงเบราว์เซอร์ได้: ไม่มี useState ไม่มี useEffect ไม่มีตัวจัดการเหตุการณ์ มันไม่เคยเรนเดอร์ใหม่บนไคลเอนต์

JSX
// Server component: never sent to the client.
export default async function NotesPage() {
  const notes = await db.notes.findAll();
  return (
    <ul>
      {notes.map((note) => (
        <li key={note.id}>{note.title}</li>
      ))}
    </ul>
  );
}
server component แบบ async สอบถามฐานข้อมูลโดยตรง

การโต้ตอบ (interactivity) อยู่ใน client components ที่ถูกทำเครื่องหมายด้วยไดเรกทีฟ 'use client' ที่ด้านบนสุดของไฟล์ ไดเรกทีฟนี้ไม่ได้ทำเครื่องหมายว่า "เป็น client component" แต่เป็น ขอบเขต (boundary): ทุกสิ่งที่โมดูลนั้น import ก็ย้ายไปฝั่งไคลเอนต์ด้วย พร็อพที่ข้ามขอบเขตต้อง ซีเรียลไลซ์ได้ (serializable): อ็อบเจกต์ธรรมดา อาร์เรย์ promise... แต่ไม่มีวันเป็นฟังก์ชันทั่วไปหรืออินสแตนซ์ของคลาส

JSX
'use client';

import { useState } from 'react';

export function LikeButton({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count} likes
    </button>
  );
}
ไดเรกทีฟ 'use client' กำหนดขอบเขตของโซนที่โต้ตอบได้

React 19 แนะนำ use ซึ่งเป็น API ที่อ่านค่าของ promise หรือ คอนเท็กซ์ ไม่เหมือนฮุก use สามารถถูกเรียก ภายในเงื่อนไขหรือลูป ได้ เมื่อได้รับ promise มันจะพักคอมโพเนนต์ไว้จนกว่าจะคลี่คลาย โดยอาศัยขอบเขต Suspense ที่ใกล้ที่สุด แพตเทิร์นที่เป็นสำนวน (idiomatic): server component สร้าง promise โดยไม่ await มัน ส่งมันเป็นพร็อพ และ client component บริโภค มันด้วย use: เซิร์ฟเวอร์เริ่มสตรีมหน้าเว็บโดยไม่ต้องรอข้อมูล

JSX
import { use, Suspense } from 'react';

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);
  return comments.map((c) => (
    <p key={c.id}>{c.text}</p>
  ));
}

function Page({ commentsPromise }) {
  return (
    <Suspense fallback={<Spinner />}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}
use พักคอมโพเนนต์ไว้จนกว่า promise จะคลี่คลาย

ในด้านการโต้ตอบ useTransition ทำเครื่องหมายการอัปเดตว่าไม่ด่วน และเปิดเผย isPending เพื่อให้คุณแสดงตัวบ่งชี้เบา ๆ ได้โดยไม่บล็อกอินพุต useOptimistic เติมเต็มคู่นี้สำหรับการเปลี่ยนแปลง (mutation) ผ่านเครือข่าย: คุณแสดงสถานะที่หวังไว้ทันทีในขณะที่แอ็กชันแบบอะซิงโครนัสกำลังรัน; เมื่อแอ็กชันสิ้นสุด (สำเร็จหรือล้มเหลว) React ทิ้งค่า optimistic และแสดงสถานะจริงอีกครั้ง ซึ่งเป็นสถานะจากแหล่งความจริง (source of truth)

JSX
const [optimisticLikes, addOptimisticLike] =
  useOptimistic(likes, (count, delta) => count + delta);

function handleLike() {
  startTransition(async () => {
    addOptimisticLike(1);
    await saveLike();
    // At the end, React shows the real state again.
  });
}
อินเทอร์เฟซตอบสนองทันที เซิร์ฟเวอร์ยืนยันในภายหลัง

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. server component ทำอะไรไม่ได้?
    • ถูกประกาศเป็น async และ await คำร้องขอ
    • อ่านฐานข้อมูลโดยตรง
    • ใช้ useState และตัวจัดการเหตุการณ์
    • เรนเดอร์ client components เป็นลูก
  2. กฎของฮุกข้อใดที่ API use ได้รับอนุญาตให้ฝ่าฝืน?
    • การถูกเรียกนอกคอมโพเนนต์
    • การถูกเรียกภายในเงื่อนไขหรือลูป
    • การถูกเรียกหลังการ return ออกก่อนกำหนดของโมดูล
    • การถูกเรียกภายในคลาส
  3. เกิดอะไรขึ้นกับค่า useOptimistic เมื่อแอ็กชันแบบอะซิงโครนัสสิ้นสุดลง?
    • มันกลายเป็นสเตตถาวรของคอมโพเนนต์
    • มันถูกทิ้งไปและสถานะจริงเข้ามาแทนที่อีกครั้ง
    • มันถูกผสานเข้ากับสถานะจริง
    • มันถูกเก็บไว้จนกว่าจะมีการเรนเดอร์ด้วยตนเองครั้งถัดไป