ทำให้เว็บ React รู้สึกเร็วด้วย Skeleton Loading และ Optimistic UI
Skeleton Loading และ Optimistic UI ช่วยให้เว็บ React ตอบสนองต่อผู้ใช้ได้อย่างเป็นธรรมชาติ แม้ไม่ได้ลดเวลาเรียก API โดยตรง เทคนิคทั้งสองเหมาะกับคนละจังหวะ แต่มีเป้าหมายร่วมกันคือทำให้ระบบรู้สึกเร็วและมีชีวิต

ทำให้เว็บ React รู้สึกเร็วด้วย Skeleton Loading และ Optimistic UI
เว็บที่เร็วไม่ได้ชนะกันด้วยตัวเลขระดับมิลลิวินาทีเพียงอย่างเดียว แต่ยังขึ้นอยู่กับว่าผู้ใช้รับรู้การตอบสนองของระบบอย่างไรด้วย ต่อให้ backend ทำงานเร็วพอสมควร หากผู้ใช้กดปุ่มแล้วหน้าจอนิ่งหรือเห็นเพียง spinner หมุนอยู่ เว็บก็ยังให้ความรู้สึกช้าได้
Skeleton Loading และ Optimistic UI เป็นสองเทคนิคที่ช่วยแก้ปัญหานี้ในแอป React ทั้งคู่ไม่ได้ทำให้ API ประมวลผลเร็วขึ้นโดยตรง แต่ทำให้หน้าจอสื่อสารกับผู้ใช้ทันทีว่าระบบกำลังทำงานหรือรับคำสั่งแล้ว
ความเร็วที่ผู้ใช้รู้สึกสำคัญอย่างไร
ระหว่างรอข้อมูล ผู้ใช้ต้องการสัญญาณที่ช่วยยืนยันว่าหน้าเว็บไม่ได้ค้าง ส่วนหลังจากกดปุ่ม ผู้ใช้ต้องการเห็นว่าการกระทำนั้นเกิดผลแล้ว หากระบบปล่อยให้หน้าจอนิ่งในสองช่วงนี้ ความไม่แน่ใจจะทำให้เวลารอสั้น ๆ รู้สึกยาวกว่าความเป็นจริง
การออกแบบ UI ที่ดีจึงไม่ควรถามเพียงว่า “ข้อมูลโหลดเสร็จหรือยัง” แต่ควรถามด้วยว่า “ผู้ใช้รู้หรือไม่ว่าระบบกำลังตอบสนอง” การแสดงโครงหน้าอย่างรวดเร็วและการสะท้อนผลของคำสั่งทันทีช่วยให้ประสบการณ์โดยรวมลื่นไหลขึ้นอย่างชัดเจน
Skeleton Loading สื่อสารสิ่งที่จะปรากฏระหว่างรอ
Skeleton Loading คือ placeholder ที่เลียนแบบโครงสร้างของเนื้อหาจริง เช่น กล่องสีเทาแทนรูปภาพ เส้นสีเทาแทนชื่อบทความ และแถบยาวแทนรายละเอียด วิธีนี้ให้ข้อมูลกับผู้ใช้มากกว่า spinner เพราะช่วยบอกทั้งสถานะการโหลดและรูปร่างคร่าว ๆ ของสิ่งที่กำลังจะปรากฏ
ใน React เราสามารถตรวจสอบสถานะ isLoading แล้วแสดงคอมโพเนนต์ Skeleton แทนข้อมูลจริงได้ ตัวอย่างเช่น หน้ารายการสินค้าอาจแสดง CardSkeleton จำนวนหกใบ ก่อนเปลี่ยนเป็น ProductCard เมื่อได้รับข้อมูลจาก API แล้ว
function ProductList({ products, isLoading }) {
if (isLoading) {
return (
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{Array.from({ length: 6 }).map((_, index) => (
<CardSkeleton key={index} />
))}
</div>
);
}
return (
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
Skeleton ที่ออกแบบใกล้เคียงกับเนื้อหาจริงจะช่วยลดการเปลี่ยนแปลงของ layout เมื่อข้อมูลมาถึง ผู้ใช้สามารถมองเห็นโครงสร้างหน้าและเริ่มทำความเข้าใจตำแหน่งของข้อมูลได้ตั้งแต่ยังโหลดไม่เสร็จ จึงเกิดความรู้สึกว่าเว็บเริ่มทำงานแล้ว
อย่างไรก็ตาม Skeleton ไม่ควรเป็นเพียงแท่งสีเทาทั่วไปที่วางไว้กลางหน้าจอ หากหน้าจริงแสดงการ์ดสามคอลัมน์ Skeleton ก็ควรมีสามคอลัมน์และมีสัดส่วนใกล้เคียงกัน ยิ่ง placeholder สอดคล้องกับเนื้อหาจริงมากเท่าไร การเปลี่ยนผ่านหลังโหลดเสร็จก็ยิ่งเป็นธรรมชาติมากขึ้น
Optimistic UI แสดงผลก่อนเซิร์ฟเวอร์ตอบกลับ
Optimistic UI ใช้กับช่วงเวลาหลังผู้ใช้ลงมือทำบางอย่าง โดยระบบจะสมมติไว้ก่อนว่าคำขอน่าจะสำเร็จและอัปเดตหน้าจอทันที จากนั้นจึงส่ง request ไปยังเซิร์ฟเวอร์อยู่เบื้องหลัง
ตัวอย่างที่เห็นได้ชัดคือปุ่ม Like เมื่อผู้ใช้กดหัวใจ หน้าจอควรเปลี่ยนหัวใจเป็นสีแดงและเพิ่มจำนวน Like ทันที แทนที่จะรอ API ตอบกลับประมาณครึ่งวินาทีแล้วจึงค่อยเปลี่ยน เพราะแม้เวลารอจะไม่มาก แต่ความหน่วงหลังการกดสามารถทำให้ระบบดูไม่ตอบสนองได้
หัวใจของเทคนิคนี้คือการบันทึก snapshot ของสถานะเดิมก่อนเปลี่ยน UI สมมติจำนวน Like เดิมคือ 10 ระบบสามารถแสดงเป็น 11 ได้ทันที แต่หาก request ล้มเหลวจะต้องย้อนค่ากลับเป็น 10 พร้อมแจ้งผู้ใช้อย่างสุภาพว่าบันทึกไม่สำเร็จและควรลองใหม่
async function handleLike() {
const previousCount = likeCount;
setLiked(true);
setLikeCount(previousCount + 1);
try {
await sendLikeRequest();
} catch {
setLiked(false);
setLikeCount(previousCount);
showMessage("บันทึกไม่สำเร็จ กรุณาลองใหม่อีกครั้ง");
}
}
แนวคิดนี้ทำได้ด้วย state ปกติของ React หรือใช้เครื่องมืออย่าง React Query และ TanStack Query เพื่อจัดการ cache ล่วงหน้า การทำงานมักแบ่งเป็นช่วงเตรียมข้อมูลเดิมก่อนเปลี่ยน ย้อนกลับเมื่อเกิดข้อผิดพลาด และปรับข้อมูลให้ตรงกับเซิร์ฟเวอร์หลัง request สิ้นสุด
เลือกใช้ Optimistic UI กับการกระทำที่เหมาะสม
Optimistic UI เหมาะกับการกระทำที่เกิดบ่อย ย้อนกลับได้ และมีโอกาสสำเร็จสูง เพราะผู้ใช้จะได้รับประโยชน์จากการตอบสนองทันทีโดยมีความเสี่ยงต่ำ ตัวอย่างที่เหมาะสมประกอบด้วย:
- กด Like หรือเพิ่ม reaction
- บันทึกรายการไว้ดูภายหลัง
- เปิดหรือปิดสถานะของงานในรายการ Todo
- จัดลำดับรายการใหม่
- สลับค่าตัวเลือกที่สามารถย้อนกลับได้
ในทางกลับกัน การกระทำที่มีผลกระทบสูงไม่ควรแสดงว่าสำเร็จก่อนระบบยืนยัน เช่น การโอนเงิน การชำระเงิน หรือการลบข้อมูลอย่างถาวร งานประเภทนี้ควรแสดงสถานะกำลังดำเนินการให้ชัดเจนและรอผลยืนยันจากเซิร์ฟเวอร์ก่อนแจ้งความสำเร็จ
Skeleton Loading และ Optimistic UI ทำงานคนละจังหวะ
แม้ทั้งสองเทคนิคจะช่วยให้เว็บรู้สึกเร็วขึ้นเหมือนกัน แต่ตอบโจทย์คนละช่วงของการใช้งาน Skeleton Loading ดูแลความรู้สึกระหว่างที่ระบบกำลังดึงข้อมูล ส่วน Optimistic UI ดูแลความรู้สึกหลังผู้ใช้ส่งการกระทำไปยังระบบ
| จังหวะการใช้งาน | เทคนิคที่เหมาะสม | สิ่งที่ผู้ใช้รับรู้ |
|---|---|---|
| กำลังรอข้อมูลปรากฏ | Skeleton Loading | ระบบกำลังโหลดและเนื้อหาจะมีโครงสร้างแบบใด |
| เพิ่งกดหรือเปลี่ยนแปลงข้อมูล | Optimistic UI | ระบบรับการกระทำและแสดงผลให้แล้ว |
| การกระทำมีความเสี่ยงสูง | รอการยืนยันจากเซิร์ฟเวอร์ | ผลลัพธ์ยังไม่ถือว่าสำเร็จจนกว่าจะได้รับการยืนยัน |
แอปหนึ่งสามารถใช้ทั้งสองแนวทางร่วมกันได้ เช่น หน้าแรกแสดง Skeleton ระหว่างโหลดรายการบทความ และใช้ Optimistic UI เมื่อผู้ใช้กดบันทึกบทความไว้ภายหลัง สิ่งสำคัญคือเลือกเทคนิคให้ตรงกับจังหวะและเตรียมสถานะข้อผิดพลาดไว้เสมอ
สรุป
Skeleton Loading คือคำตอบระหว่างรอข้อมูล โดยแสดงโครงสร้างที่ใกล้เคียงกับเนื้อหาจริงเพื่อให้ผู้ใช้รู้ว่าระบบกำลังทำงาน ส่วน Optimistic UI คือคำตอบหลังผู้ใช้ลงมือ โดยแสดงผลล่วงหน้าและย้อนกลับได้หากเซิร์ฟเวอร์ปฏิเสธคำขอ
เมื่อใช้ทั้งสองเทคนิคอย่างเหมาะสม เว็บ React จะไม่ได้เพียงดูเร็วขึ้น แต่จะตอบสนองอย่างเป็นธรรมชาติและสร้างความมั่นใจให้ผู้ใช้มากขึ้น ความเร็วที่ดีจึงไม่ใช่แค่เรื่องของ latency แต่คือการทำให้ทุกช่วงเวลารอมีคำอธิบายและทุกการกระทำได้รับการตอบสนองอย่างชัดเจน