สำรวจ server components, การอ่าน promise ด้วย use, และการอัปเดตแบบมองโลกในแง่ดี (optimistic updates) ของ React 19
เปิดบทเรียนนี้ใน KodokonReact Server Components (RSC) วาดเส้นแบ่งระหว่างไคลเอนต์กับเซิร์ฟเวอร์ใหม่ server component รัน บนเซิร์ฟเวอร์เท่านั้น: โค้ดของมันไม่มีวันไปถึงบันเดิลฝั่งไคลเอนต์ มันสามารถเป็น async อ่านฐานข้อมูลหรือระบบไฟล์ได้โดยตรง และเอาต์พุตของมันถูกส่งไปยังเบราว์เซอร์ในรูปของ สตรีมที่ถูกซีเรียลไลซ์ (serialized stream) (RSC payload) ที่ React ผสานเข้ากับต้นไม้ฝั่งไคลเอนต์ ในทางกลับกัน มัน ไม่มีสเตต ไม่มีเอฟเฟกต์ และไม่สามารถเข้าถึงเบราว์เซอร์ได้: ไม่มี useState ไม่มี useEffect ไม่มีตัวจัดการเหตุการณ์ มันไม่เคยเรนเดอร์ใหม่บนไคลเอนต์
// 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>
);
}การโต้ตอบ (interactivity) อยู่ใน client components ที่ถูกทำเครื่องหมายด้วยไดเรกทีฟ 'use client' ที่ด้านบนสุดของไฟล์ ไดเรกทีฟนี้ไม่ได้ทำเครื่องหมายว่า "เป็น client component" แต่เป็น ขอบเขต (boundary): ทุกสิ่งที่โมดูลนั้น import ก็ย้ายไปฝั่งไคลเอนต์ด้วย พร็อพที่ข้ามขอบเขตต้อง ซีเรียลไลซ์ได้ (serializable): อ็อบเจกต์ธรรมดา อาร์เรย์ promise... แต่ไม่มีวันเป็นฟังก์ชันทั่วไปหรืออินสแตนซ์ของคลาส
'use client';
import { useState } from 'react';
export function LikeButton({ initialCount }) {
const [count, setCount] = useState(initialCount);
return (
<button onClick={() => setCount(count + 1)}>
{count} likes
</button>
);
}React 19 แนะนำ use ซึ่งเป็น API ที่อ่านค่าของ promise หรือ คอนเท็กซ์ ไม่เหมือนฮุก use สามารถถูกเรียก ภายในเงื่อนไขหรือลูป ได้ เมื่อได้รับ promise มันจะพักคอมโพเนนต์ไว้จนกว่าจะคลี่คลาย โดยอาศัยขอบเขต Suspense ที่ใกล้ที่สุด แพตเทิร์นที่เป็นสำนวน (idiomatic): server component สร้าง promise โดยไม่ await มัน ส่งมันเป็นพร็อพ และ client component บริโภค มันด้วย use: เซิร์ฟเวอร์เริ่มสตรีมหน้าเว็บโดยไม่ต้องรอข้อมูล
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>
);
}ในด้านการโต้ตอบ useTransition ทำเครื่องหมายการอัปเดตว่าไม่ด่วน และเปิดเผย isPending เพื่อให้คุณแสดงตัวบ่งชี้เบา ๆ ได้โดยไม่บล็อกอินพุต useOptimistic เติมเต็มคู่นี้สำหรับการเปลี่ยนแปลง (mutation) ผ่านเครือข่าย: คุณแสดงสถานะที่หวังไว้ทันทีในขณะที่แอ็กชันแบบอะซิงโครนัสกำลังรัน; เมื่อแอ็กชันสิ้นสุด (สำเร็จหรือล้มเหลว) React ทิ้งค่า optimistic และแสดงสถานะจริงอีกครั้ง ซึ่งเป็นสถานะจากแหล่งความจริง (source of truth)
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.
});
}use ได้รับอนุญาตให้ฝ่าฝืน?useOptimistic เมื่อแอ็กชันแบบอะซิงโครนัสสิ้นสุดลง?