ในโลกของการพัฒนาเว็บสมัยใหม่ การจัดการสถานะข้อมูลฝั่งไคลเอ็นต์ (client-side data) ที่ซับซ้อนและมีการเรียกใช้ API บ่อยครั้งเป็นความท้าทายที่นักพัฒนาต้องเผชิญ **TanStack React Query** (เดิมคือ React Query) ได้เข้ามาเปลี่ยนแปลงวิธีการจัดการข้อมูลเหล่านี้อย่างสิ้นเชิง ด้วย hooks ที่ทรงพลัง โดยเฉพาะอย่างยิ่ง **`useQuery`** ที่ช่วยให้เราสามารถจัดการ fetching, caching, synchronizing, และ updating server state ได้อย่างง่ายดายและมีประสิทธิภาพ
บทความนี้จะแนะนำให้คุณรู้จักกับ `useQuery` และแสดงให้เห็นว่ามันสามารถทำให้โค้ดของคุณสะอาดขึ้น มีประสิทธิภาพมากขึ้น และประสบการณ์ผู้ใช้ดีขึ้นได้อย่างไร
-----
### ทำไมต้องใช้ TanStack React Query?
ก่อนที่เราจะเจาะลึกถึง `useQuery` เรามาดูกันว่าทำไม React Query ถึงเป็นที่นิยมและมีความสำคัญ:
* **Caching อัตโนมัติ:** จัดการแคชข้อมูลที่ดึงมาแล้วโดยอัตโนมัติ ลดการเรียก API ซ้ำซ้อน
* **Background Refetching:** อัปเดตข้อมูลในเบื้องหลังเพื่อให้ข้อมูลเป็นปัจจุบันอยู่เสมอ โดยไม่บล็อก UI
* **Optimistic Updates:** ทำให้ UI ตอบสนองได้รวดเร็วขึ้นโดยการอัปเดตข้อมูลทันที และย้อนกลับหากเกิดข้อผิดพลาด
* **Devtools:** มีเครื่องมือ Devtools ที่ยอดเยี่ยมสำหรับการตรวจสอบสถานะของ Query และ Cache
* **จัดการสถานะได้ง่ายขึ้น:** ลด boilerplate code ที่เกี่ยวข้องกับการ fetching data (loading, error, success states)
* **ประสิทธิภาพที่ดีขึ้น:** ช่วยให้แอปพลิเคชันของคุณทำงานได้เร็วและราบรื่นขึ้น
-----
### การเริ่มต้นใช้งาน
ก่อนอื่น คุณต้องติดตั้ง TanStack React Query ในโปรเจกต์ของคุณ:
```bash
npm install @tanstack/react-query
# หรือ
yarn add @tanstack/react-query
```
จากนั้น คุณต้องห่อหุ้มแอปพลิเคชันของคุณด้วย **`QueryClientProvider`** เพื่อให้ `useQuery` สามารถเข้าถึง QueryClient ได้:
```jsx
// src/main.jsx หรือ src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; // ไม่บังคับ
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')).render(
{/* แสดง Devtools */}
,
);
```
-----
### ทำความเข้าใจกับ `useQuery`
`useQuery` เป็น React Hook ที่ใช้สำหรับ "fetching" ข้อมูลจากเซิร์ฟเวอร์ มันจะส่งคืนสถานะของ Query (loading, error, success) พร้อมกับข้อมูลที่ได้มา
**โครงสร้างพื้นฐาน:**
```javascript
const { data, isLoading, isError, error, isSuccess, status } = useQuery({
queryKey: ['uniqueKey'],
queryFn: async () => {
// ฟังก์ชันสำหรับ fetch ข้อมูล
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
},
// options เพิ่มเติม (เช่น staleTime, cacheTime, retry, enabled ฯลฯ)
});
```
**พารามิเตอร์ที่สำคัญ:**
1. **`queryKey` (Array):**
* นี่คือ "**คีย์**" ที่ไม่ซ้ำกันสำหรับ Query ของคุณ
* React Query ใช้ `queryKey` เพื่อจัดการ Caching, Refetching และ Shared State
* ควรเป็น Array เสมอ หากข้อมูลที่คุณดึงมาขึ้นอยู่กับพารามิเตอร์ใดๆ ให้รวมพารามิเตอร์เหล่านั้นไว้ใน `queryKey` ด้วย
ตัวอย่าง:
* `['todos']`
* `['todo', todoId]`
* `['posts', { type: 'published', authorId: 123 }]`
2. **`queryFn` (Function):**
* ฟังก์ชัน `async` ที่รับผิดชอบในการ fetch ข้อมูล
* จะต้องคืนค่าเป็น Promise ที่ Resolve ด้วยข้อมูล หรือ Reject ด้วย Error
**ค่าที่ส่งคืนจาก `useQuery`:**
* **`data`:** ข้อมูลที่ดึงมาได้เมื่อ Query สำเร็จ (จะเป็น `undefined` ในขณะที่ `isLoading` หรือ `isError`)
* **`isLoading` / `isPending`:** (ใน v5 เปลี่ยนเป็น **`isPending`** เพื่อให้ความหมายชัดเจนขึ้นว่าอยู่ในสถานะรอ) Boolean ที่ระบุว่า Query กำลังอยู่ในระหว่างการ fetch ข้อมูลครั้งแรก
* **`isError`:** Boolean ที่ระบุว่า Query มีข้อผิดพลาดเกิดขึ้น
* **`error`:** อ็อบเจกต์ Error หาก `isError` เป็น `true`
* **`isSuccess`:** Boolean ที่ระบุว่า Query สำเร็จและมีข้อมูลพร้อมใช้งาน
* **`status`:** สถานะปัจจุบันของ Query (`'pending'`, `'error'`, `'success'`)
-----
### ตัวอย่างการใช้งานจริง
มาดูตัวอย่างการดึงข้อมูลรายชื่อโพสต์จาก API ง่ายๆ:
```jsx
// src/components/PostsList.jsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';
async function fetchPosts() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
return response.json();
}
function PostsList() {
const { data: posts, isLoading, isError, error } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
if (isLoading) {
return Loading posts...;
}
if (isError) {
return Error: {error.message};
}
return (
Posts
{posts.map((post) => (
-
{post.title}
{post.body}
))}
);
}
export default PostsList;
```
**คำอธิบาย:**
1. เราสร้างฟังก์ชัน `fetchPosts` แยกออกมาเพื่อทำหน้าที่เรียก API
2. เรียกใช้ `useQuery` โดยมี **`queryKey`** เป็น `['posts']` และ **`queryFn`** เป็น `fetchPosts`
3. ตรวจสอบสถานะ **`isLoading`**, **`isError`** และแสดง UI ที่เหมาะสม
4. เมื่อข้อมูลพร้อมใช้งาน (`isSuccess` เป็น `true`) เราก็สามารถเข้าถึง `posts` และนำไปแสดงผลได้เลย
-----
### การจัดการพารามิเตอร์ใน `queryKey`
บ่อยครั้งที่เราต้องการดึงข้อมูลโดยอิงจาก ID หรือพารามิเตอร์อื่นๆ `queryKey` สามารถจัดการสิ่งนี้ได้อย่างง่ายดาย:
```jsx
// src/components/PostDetail.jsx
import React from 'react';
import { useQuery } from '@tanstack/react-query';
async function fetchPostById(postId) {
const response = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`);
if (!response.ok) {
throw new Error(`Failed to fetch post with ID: ${postId}`);
}
return response.json();
}
function PostDetail({ postId }) {
const { data: post, isLoading, isError, error } = useQuery({
queryKey: ['post', postId], // คีย์จะเปลี่ยนไปตาม postId
queryFn: () => fetchPostById(postId), // ส่ง postId เข้าไปในฟังก์ชัน fetch
enabled: !!postId, // จะเรียก Query ก็ต่อเมื่อ postId มีค่า (ไม่เป็น null/undefined)
});
if (isLoading) {
return Loading post...;
}
if (isError) {
return Error: {error.message};
}
if (!post) { // กรณีที่ postId ไม่มีค่าตั้งแต่แรก
return Please select a post.
}
return (
{post.title}
{post.body}
);
}
export default PostDetail;
```
**คำอธิบายเพิ่มเติม:**
* **`queryKey: ['post', postId]`:** เมื่อ `postId` เปลี่ยนไป React Query จะรู้ว่านี่คือ Query ใหม่และจะทำการ fetch ข้อมูลใหม่
* **`queryFn: () => fetchPostById(postId)`:** เราใช้ Arrow Function เพื่อส่ง `postId` ไปยัง `fetchPostById`
* **`enabled: !!postId`:** นี่คือ Option ที่สำคัญมาก\! มันบอกให้ `useQuery` จะทำการเรียก `queryFn` ก็ต่อเมื่อ `postId` มีค่าเป็น truthy เท่านั้น (ไม่เป็น `null` หรือ `undefined`) ซึ่งมีประโยชน์มากเมื่อเราอาจจะยังไม่มี `postId` ในตอนเริ่มต้น
-----
### Options ที่มีประโยชน์อื่นๆ
`useQuery` มี Options มากมายให้เราปรับแต่งพฤติกรรมได้:
* **`staleTime` (number | Infinity):**
* กำหนดระยะเวลา (มิลลิวินาที) ที่ข้อมูลจะถือว่า "สด" (fresh)
* ภายใน `staleTime`, React Query จะไม่ทำการ fetch ข้อมูลใหม่
* ค่าเริ่มต้นคือ `0` (ข้อมูลจะถือว่า stale ทันทีหลังจาก fetch ครั้งแรก)
* ถ้าตั้งค่าเป็น `Infinity` ข้อมูลจะไม่มีวัน stale และจะไม่มีการ refetch โดยอัตโนมัติ
* **`cacheTime` (number | Infinity):**
* กำหนดระยะเวลา (มิลลิวินาที) ที่ข้อมูลจะถูกเก็บไว้ใน cache หลังจากที่ Query ไม่ถูกใช้งานแล้ว
* เมื่อ Query ไม่ถูกใช้งานและ `cacheTime` หมดลง ข้อมูลจะถูก garbage-collected
* ค่าเริ่มต้นคือ `5 * 60 * 1000` (5 นาที)
* **`refetchOnWindowFocus` (boolean | 'always'):**
* กำหนดว่าจะให้ Query ทำการ refetch เมื่อหน้าต่างเบราว์เซอร์กลับมาโฟกัสหรือไม่
* ค่าเริ่มต้นคือ `true`
* **`retry` (boolean | number):**
* กำหนดว่าจะให้ Query ทำการ retry เมื่อเกิดข้อผิดพลาดในการ fetch หรือไม่
* ค่าเริ่มต้นคือ `3` ครั้ง
* ตั้งค่าเป็น `false` เพื่อปิดการ retry
* **`onSuccess`, `onError`, `onSettled` (Function):**
* Callback functions ที่จะถูกเรียกเมื่อ Query สำเร็จ, มีข้อผิดพลาด หรือไม่ว่าจะสำเร็จหรือมีข้อผิดพลาดก็ตาม
* **`select` (Function):**
* ใช้สำหรับแปลงหรือเลือกข้อมูลบางส่วนจาก `data` ที่ได้มา ก่อนที่จะส่งคืนให้ Component
```javascript
const { data: postTitles } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
select: (posts) => posts.map(post => post.title), // เลือกแค่ title
});
// postTitles จะเป็น array ของ strings
```
-----
### สรุป
`useQuery` ของ TanStack React Query เป็นเครื่องมือที่ทรงพลังและขาดไม่ได้สำหรับการจัดการข้อมูลในแอปพลิเคชัน React สมัยใหม่ มันช่วยลดความซับซ้อนของการจัดการสถานะการโหลด, ข้อผิดพลาด, และการแคชข้อมูล ทำให้คุณสามารถโฟกัสกับการสร้างฟีเจอร์ได้มากขึ้น
การทำความเข้าใจ `queryKey` และ `queryFn` รวมถึง Options ต่างๆ จะช่วยให้คุณสามารถใช้ประโยชน์จาก React Query ได้อย่างเต็มที่ และสร้างแอปพลิเคชันที่ตอบสนองได้ดีและมีประสิทธิภาพ
ลองนำ `useQuery` ไปใช้ในโปรเจกต์ถัดไปของคุณ แล้วคุณจะเห็นถึงความแตกต่างอย่างแน่นอน\!

0 ความคิดเห็น