คู่มือฉบับสมบูรณ์: React's use Resource Hook | MLOG | MLOG คู่มือฉบับสมบูรณ์: React's use Resource Hook
Hook use:
ใน React นำเสนอวิธีการที่ทรงพลังและเป็นแบบ declarative สำหรับการจัดการโหลดทรัพยากรและการดึงข้อมูลโดยตรงภายในคอมโพเนนต์ของคุณ มันช่วยให้คุณสามารถระงับการเรนเดอร์ (suspend rendering) จนกว่าทรัพยากรจะพร้อมใช้งาน ซึ่งนำไปสู่ประสบการณ์ผู้ใช้ที่ดีขึ้นและการจัดการข้อมูลที่ง่ายขึ้น คู่มือนี้จะสำรวจ hook use:
อย่างละเอียด ครอบคลุมพื้นฐาน กรณีการใช้งานขั้นสูง และแนวทางปฏิบัติที่ดีที่สุด
use:
Hook คืออะไร?
Hook use:
เป็น React hook พิเศษที่ออกแบบมาเพื่อทำงานร่วมกับ Suspense ซึ่ง Suspense เป็นกลไกที่ช่วยให้คอมโพเนนต์สามารถ "รอ" บางสิ่งบางอย่างก่อนที่จะเรนเดอร์ เช่น การรอข้อมูลจาก API โดย hook use:
จะอนุญาตให้คอมโพเนนต์ "อ่าน" ค่าจาก promise หรือทรัพยากรอื่นได้โดยตรง และจะระงับการทำงานของคอมโพเนนต์จนกว่าทรัพยากรนั้นจะถูก resolve หรือพร้อมใช้งาน แนวทางนี้ส่งเสริมวิธีการจัดการกับการทำงานแบบอะซิงโครนัส (asynchronous operations) ที่เป็นแบบ declarative และมีประสิทธิภาพมากกว่าวิธีดั้งเดิม เช่น การใช้ useEffect
และไลบรารีการจัดการ state
ทำไมถึงควรใช้ use:
?
นี่คือเหตุผลที่คุณควรพิจารณาใช้ use:
hook:
การดึงข้อมูลที่ง่ายขึ้น: ไม่จำเป็นต้องจัดการ state ด้วยตนเองและเรียกใช้ useEffect
เพื่อดึงข้อมูล
แนวทางแบบ Declarative: แสดงการพึ่งพาข้อมูล (data dependencies) ได้อย่างชัดเจนโดยตรงภายในคอมโพเนนต์
ประสบการณ์ผู้ใช้ที่ดีขึ้น: Suspense ช่วยให้การเปลี่ยนผ่านและสถานะการโหลดเป็นไปอย่างราบรื่น
ประสิทธิภาพที่ดีขึ้น: ลดการ re-render ที่ไม่จำเป็นและเพิ่มประสิทธิภาพการโหลดทรัพยากร
โค้ดที่อ่านง่ายขึ้น: ทำให้ตรรกะของคอมโพเนนต์ง่ายขึ้นและเพิ่มความสามารถในการบำรุงรักษา
พื้นฐานของ use:
การใช้งานพื้นฐาน
Hook use:
รับ promise (หรืออ็อบเจกต์ thenable ใดๆ) เป็นอาร์กิวเมนต์และจะคืนค่าที่ resolve แล้วของ promise นั้น หาก promise ยังคงอยู่ในสถานะ pending คอมโพเนนต์จะถูกระงับ (suspend) นี่คือตัวอย่างง่ายๆ:
ตัวอย่างที่ 1: การดึงและแสดงข้อมูล
สมมติว่าเราต้องการดึงข้อมูลผู้ใช้จาก API และนำมาแสดงผล เราสามารถใช้ use:
ได้ดังนี้:
การสร้าง Resource (ฟังก์ชันสำหรับดึงข้อมูล)
ขั้นแรก สร้างฟังก์ชันเพื่อดึงข้อมูล ฟังก์ชันนี้จะคืนค่าเป็น Promise:
async function fetchUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
}
Copy
การใช้ use:
ในคอมโพเนนต์
import React, { Suspense } from 'react';
async function fetchUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
}
function UserProfile({ userId }) {
const user = React.use(fetchUser(userId));
return (
{user.name}
Email: {user.email}
Phone: {user.phone}
);
}
function App() {
return (
Loading user data...
}>
);
}
export default App;
Copy
ในตัวอย่างนี้:
fetchUser
เป็นฟังก์ชันแบบอะซิงโครนัสที่ดึงข้อมูลผู้ใช้จาก API endpoint
คอมโพเนนต์ UserProfile
ใช้ React.use(fetchUser(userId))
เพื่อดึงข้อมูลผู้ใช้
คอมโพเนนต์ Suspense
จะห่อหุ้มคอมโพเนนต์ UserProfile
และมี fallback
prop ซึ่งจะแสดงผลในขณะที่กำลังดึงข้อมูล
หากข้อมูลยังไม่พร้อมใช้งาน React จะระงับการทำงานของคอมโพเนนต์ UserProfile
และแสดง UI สำรอง (ข้อความ "Loading user data...") เมื่อดึงข้อมูลเสร็จสิ้น คอมโพเนนต์ UserProfile
จะเรนเดอร์พร้อมกับข้อมูลผู้ใช้
ตัวอย่างที่ 2: การจัดการข้อผิดพลาด
Hook use:
จะจัดการข้อผิดพลาดที่ถูกโยน (throw) โดย promise โดยอัตโนมัติ หากเกิดข้อผิดพลาดขึ้น คอมโพเนนต์จะถูกระงับและ error boundary ที่ใกล้ที่สุดจะดักจับข้อผิดพลาดนั้น
import React, { Suspense } from 'react';
async function fetchUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
}
function UserProfile({ userId }) {
const user = React.use(fetchUser(userId));
return (
{user.name}
Email: {user.email}
Phone: {user.phone}
);
}
function ErrorBoundary({ children, fallback }) {
const [error, setError] = React.useState(null);
React.useEffect(() => {
const handleError = (e) => {
setError(e);
};
window.addEventListener('error', handleError);
return () => {
window.removeEventListener('error', handleError);
};
}, []);
if (error) {
return fallback;
}
return children;
}
function App() {
return (
Error loading user data.
}>
Loading user data... }>
{/* Assuming this ID doesn't exist and will cause an error */}
);
}
export default App;
Copy
ในตัวอย่างนี้ หากฟังก์ชัน fetchUser
โยนข้อผิดพลาด (เช่น เนื่องจากสถานะ 404) คอมโพเนนต์ ErrorBoundary
จะดักจับข้อผิดพลาดและแสดง UI สำรอง ซึ่ง fallback สามารถเป็นคอมโพเนนต์ React ใดก็ได้ เช่น ข้อความแสดงข้อผิดพลาดหรือปุ่มลองอีกครั้ง
เทคนิคขั้นสูงกับ use:
1. การแคช (Caching) ทรัพยากร
เพื่อหลีกเลี่ยงการดึงข้อมูลซ้ำซ้อน คุณสามารถแคชทรัพยากร (Promise) และนำกลับมาใช้ใหม่ในหลายๆ คอมโพเนนต์หรือการเรนเดอร์ได้ การปรับปรุงประสิทธิภาพนี้มีความสำคัญอย่างยิ่ง
import React, { Suspense, useRef } from 'react';
const resourceCache = new Map();
async function fetchUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
}
function getUserResource(userId) {
if (!resourceCache.has(userId)) {
resourceCache.set(userId, {
read() {
if (!this.promise) {
this.promise = fetchUser(userId);
}
if (this.result) {
return this.result;
}
throw this.promise;
}
});
}
return resourceCache.get(userId);
}
function UserProfile({ userId }) {
const resource = getUserResource(userId);
const user = resource.read();
return (
{user.name}
Email: {user.email}
Phone: {user.phone}
);
}
function App() {
return (
Loading user data...
}>
);
}
export default App;
Copy
ในตัวอย่างนี้:
เราใช้ resourceCache
ซึ่งเป็น Map เพื่อจัดเก็บ Promises สำหรับ user ID ที่แตกต่างกัน
ฟังก์ชัน getUserResource
จะตรวจสอบว่ามี Promise สำหรับ user ID ที่กำหนดอยู่ในแคชแล้วหรือไม่ ถ้ามี ก็จะคืนค่า Promise ที่แคชไว้ ถ้าไม่มี ก็จะสร้าง Promise ใหม่ เก็บไว้ในแคช แล้วจึงคืนค่าออกไป
วิธีนี้ช่วยให้มั่นใจได้ว่าเราจะดึงข้อมูลผู้ใช้เพียงครั้งเดียว แม้ว่าคอมโพเนนต์ UserProfile
จะถูกเรนเดอร์หลายครั้งด้วย user ID เดียวกันก็ตาม
2. การใช้ use:
กับ Server Components
Hook use:
มีประโยชน์อย่างยิ่งใน React Server Components ซึ่งสามารถทำการดึงข้อมูลได้โดยตรงบนเซิร์ฟเวอร์ ส่งผลให้การโหลดหน้าเว็บครั้งแรกเร็วขึ้นและปรับปรุง SEO ได้ดีขึ้น
ตัวอย่างกับ Next.js Server Component
// app/user/[id]/page.jsx (Server Component in Next.js)
import React from 'react';
async function fetchUser(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
if (!response.ok) {
throw new Error(`Failed to fetch user: ${response.status}`);
}
return response.json();
}
export default async function UserPage({ params }) {
const user = React.use(fetchUser(params.id));
return (
{user.name}
Email: {user.email}
Phone: {user.phone}
);
}
Copy
ใน Next.js server component นี้ ฟังก์ชัน fetchUser
จะดึงข้อมูลผู้ใช้บนเซิร์ฟเวอร์ Hook use:
จะระงับคอมโพเนนต์จนกว่าข้อมูลจะพร้อมใช้งาน ซึ่งช่วยให้การเรนเดอร์ฝั่งเซิร์ฟเวอร์ (server-side rendering) มีประสิทธิภาพ
แนวทางปฏิบัติที่ดีที่สุดสำหรับ use:
แคชทรัพยากร: ควรแคชทรัพยากรของคุณเสมอเพื่อหลีกเลี่ยงการดึงข้อมูลซ้ำซ้อน ใช้ useRef
หรือ global cache เพื่อการนี้
จัดการข้อผิดพลาด: ห่อหุ้มคอมโพเนนต์ของคุณด้วย Suspense
และ error boundaries เพื่อจัดการสถานะการโหลดและข้อผิดพลาดอย่างเหมาะสม
ใช้กับ Server Components: ใช้ประโยชน์จาก use:
ใน server components เพื่อเพิ่มประสิทธิภาพการดึงข้อมูลและปรับปรุง SEO
หลีกเลี่ยงการดึงข้อมูลเกินความจำเป็น: ดึงเฉพาะข้อมูลที่จำเป็นเพื่อลดภาระของเครือข่าย
ปรับปรุงขอบเขตของ Suspense: วาง suspense boundaries อย่างมีกลยุทธ์เพื่อหลีกเลี่ยงการระงับการทำงานของส่วนใหญ่ของแอปพลิเคชัน
การจัดการข้อผิดพลาดแบบ Global: ใช้ global error boundaries เพื่อดักจับข้อผิดพลาดที่ไม่คาดคิดและมอบประสบการณ์ผู้ใช้ที่สม่ำเสมอ
ตัวอย่างการใช้งานจริง
1. รายการสินค้า E-commerce
ลองนึกภาพเว็บไซต์ E-commerce ที่แสดงรายการสินค้า การ์ดสินค้าแต่ละใบสามารถใช้ use:
เพื่อดึงรายละเอียดของสินค้าได้:
// ProductCard.jsx
import React, { Suspense } from 'react';
async function fetchProduct(productId) {
const response = await fetch(`/api/products/${productId}`);
if (!response.ok) {
throw new Error(`Failed to fetch product: ${response.status}`);
}
return response.json();
}
function ProductCard({ productId }) {
const product = React.use(fetchProduct(productId));
return (
{product.name}
{product.description}
Price: ${product.price}
);
}
function ProductList({ productIds }) {
return (
{productIds.map((productId) => (
Loading product...
}>
))}
);
}
export default ProductList;
Copy
แนวทางนี้ช่วยให้มั่นใจได้ว่าการ์ดสินค้าแต่ละใบจะโหลดอย่างอิสระต่อกัน และการเรนเดอร์หน้าเว็บโดยรวมจะไม่ถูกบล็อกโดยสินค้าที่โหลดช้า ผู้ใช้จะเห็นตัวบ่งชี้การโหลดแยกกันสำหรับแต่ละสินค้า ซึ่งมอบประสบการณ์ที่ดีกว่า
2. ฟีดโซเชียลมีเดีย
ฟีดโซเชียลมีเดียสามารถใช้ use:
เพื่อดึงโปรไฟล์ผู้ใช้ โพสต์ และความคิดเห็นได้:
// Post.jsx
import React, { Suspense } from 'react';
async function fetchPost(postId) {
const response = await fetch(`/api/posts/${postId}`);
if (!response.ok) {
throw new Error(`Failed to fetch post: ${response.status}`);
}
return response.json();
}
async function fetchComments(postId) {
const response = await fetch(`/api/posts/${postId}/comments`);
if (!response.ok) {
throw new Error(`Failed to fetch comments: ${response.status}`);
}
return response.json();
}
function Comments({ postId }) {
const comments = React.use(fetchComments(postId));
return (
{comments.map((comment) => (
{comment.text}
))}
);
}
function Post({ postId }) {
const post = React.use(fetchPost(postId));
return (
{post.title}
{post.content}
Loading comments... }>
);
}
export default Post;
Copy
ตัวอย่างนี้ใช้ Suspense boundaries ซ้อนกันเพื่อโหลดเนื้อหาของโพสต์และความคิดเห็นอย่างเป็นอิสระต่อกัน ผู้ใช้สามารถเห็นเนื้อหาของโพสต์ในขณะที่ความคิดเห็นยังคงกำลังโหลดอยู่
ข้อผิดพลาดที่พบบ่อยและวิธีหลีกเลี่ยง
ไม่แคชทรัพยากร: การลืมแคชทรัพยากรอาจนำไปสู่ปัญหาด้านประสิทธิภาพ ควรใช้กลไกการแคชเสมอ เช่น useRef
หรือ global cache
การระงับการทำงานมากเกินไป: การระงับการทำงานของส่วนใหญ่ของแอปพลิเคชันอาจส่งผลให้ประสบการณ์ผู้ใช้ไม่ดี ควรวาง suspense boundaries อย่างมีกลยุทธ์
ละเลยข้อผิดพลาด: การไม่จัดการข้อผิดพลาดอาจนำไปสู่พฤติกรรมที่ไม่คาดคิด ควรใช้ error boundaries เพื่อดักจับและจัดการข้อผิดพลาดอย่างเหมาะสมเสมอ
การใช้ API ที่ไม่ถูกต้อง: ตรวจสอบให้แน่ใจว่า API endpoints ของคุณเชื่อถือได้และคืนข้อมูลในรูปแบบที่คาดหวัง
การ Re-render ที่ไม่จำเป็น: หลีกเลี่ยงการ re-render ที่ไม่จำเป็นโดยใช้ React.memo
และปรับปรุงตรรกะการเรนเดอร์ของคอมโพเนนต์ของคุณ
ทางเลือกอื่นนอกจาก use:
แม้ว่า use:
จะมีประโยชน์อย่างมาก แต่ก็มีแนวทางอื่นในการดึงข้อมูลใน React:
useEffect
กับ State: แนวทางดั้งเดิมที่ใช้ useEffect
เพื่อดึงข้อมูลและเก็บไว้ใน state วิธีนี้มีโค้ดที่ยาวกว่าและต้องจัดการ state ด้วยตนเอง
useSWR
: ไลบรารี React Hook ยอดนิยมสำหรับการดึงข้อมูลระยะไกล useSWR
มีฟีเจอร์ต่างๆ เช่น การแคช การ revalidation และการจัดการข้อผิดพลาด
useQuery
จาก React Query: อีกหนึ่งไลบรารีที่ทรงพลังสำหรับการจัดการข้อมูลแบบอะซิงโครนัส React Query มีฟีเจอร์ขั้นสูง เช่น การอัปเดตในเบื้องหลัง, optimistic updates, และการลองใหม่โดยอัตโนมัติ
Relay: เฟรมเวิร์ก JavaScript สำหรับการสร้างแอปพลิเคชัน React ที่ขับเคลื่อนด้วยข้อมูล Relay นำเสนอแนวทางแบบ declarative ในการดึงและจัดการข้อมูล
การเลือกระหว่างทางเลือกเหล่านี้ขึ้นอยู่กับความซับซ้อนของแอปพลิเคชันและข้อกำหนดเฉพาะของคุณ สำหรับสถานการณ์การดึงข้อมูลที่ไม่ซับซ้อน use:
อาจเป็นตัวเลือกที่ยอดเยี่ยม สำหรับสถานการณ์ที่ซับซ้อนมากขึ้น ไลบรารีเช่น useSWR
หรือ React Query อาจเหมาะสมกว่า
สรุป
Hook use:
ใน React เป็นวิธีการที่ทรงพลังและเป็นแบบ declarative สำหรับการจัดการโหลดทรัพยากรและการดึงข้อมูล การใช้ประโยชน์จาก use:
ร่วมกับ Suspense จะช่วยให้คุณลดความซับซ้อนของตรรกะในคอมโพเนนต์ ปรับปรุงประสบการณ์ผู้ใช้ และเพิ่มประสิทธิภาพการทำงาน คู่มือนี้ได้ครอบคลุมพื้นฐาน เทคนิคขั้นสูง และแนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้ use:
ในแอปพลิเคชัน React ของคุณ การปฏิบัติตามแนวทางเหล่านี้จะช่วยให้คุณสามารถจัดการการทำงานแบบอะซิงโครนัสได้อย่างมีประสิทธิภาพ และสร้างแอปพลิเคชันที่แข็งแกร่ง มีประสิทธิภาพสูง และเป็นมิตรต่อผู้ใช้ ในขณะที่ React ยังคงพัฒนาต่อไป การเรียนรู้เทคนิคอย่าง use:
ให้เชี่ยวชาญจึงเป็นสิ่งสำคัญเพื่อที่จะก้าวทันและมอบประสบการณ์ผู้ใช้ที่ยอดเยี่ยม
แหล่งข้อมูลอ้างอิง