React: TanStack Query (React Query)
آخر تحديث: 2026-08-26
في المحاضرة السابقة، استخدم توم «الـhooks» المخصصة و«axios» لتغليف طلبات HTTP، لكن ظهرت مشكلات جديدة: فكل من لوحة التحكم والشريط الجانبي يعرضان عدد المستخدمين، ويقوم كل مكون بإرسال طلبه الخاص (مما يؤدي إلى إهدار النطاق الترددي وموارد الخادم)؛ وعندما يغير المستخدم اسم المستخدم الخاص به في الصفحة «أ»، تظل البيانات في الصفحة «ب» قديمة؛ وبعد إرسال نموذج التعديل، يتعين على المستخدم تشغيل عملية تحديث البيانات يدويًّا. وأدرك توم أنه: هناك حاجة إلى حل «إدارة الحالة من جانب الخادم» لمعاملة بيانات واجهة برمجة التطبيقات (API) كنوع خاص من الحالات — حالة يتم تخزينها مؤقتًا، ولها مدة صلاحية، ويمكن مزامنتها تلقائيًا.
1. ما ستتعلمه
- تتولى
useQueryإدارة عملية استرجاع البيانات (الاسترجاع، والتخزين المؤقت، وإعادة الاسترجاع التلقائي) - تتولى وظيفة
useMutationإدارة عمليات كتابة البيانات (عمليات الإدراج والحذف والتحديث والاستعلامات القديمة) - تتحكم معلمتا staleTime و cacheTime في سياسة التخزين المؤقت
- تتيح التحديثات التوقعية استجابة واجهة المستخدم في الوقت الفعلي
- التمييز بين الحالة من جانب الخادم والحالة من جانب العميل
2. الرسوم التخطيطية المفاهيمية
flowchart TD
A[useQuery Call] --> B{Cache Hit?}
B -->|No matches found| C[Initiate API Request]
B -->|Hit but Expired| C
B -->|On Target and Fresh| D[Return cached data directly]
C --> E[Cached Data]
E --> F[Rendering Component]
F --> G{staleTime Has it expired??}
G -->|Not yet due| H[Data labeled as"Fresh"]
G -->|Expired| I[Data labeled as"Expired"]
I --> J{Bring the window back to the foreground?}
J -->|is | C
I --> K{refetchInterval?}
K -->|is | C
I --> L{There's something new useMutation<br/>invalidate?}
L -->|is | C
style A fill:#e1f5fe,stroke:#0288d1
style E fill:#fff3e0,stroke:#f57c00
style H fill:#e8f5e9,stroke:#388e3c
style I fill:#ffcdd2,stroke:#d32f2f
الآلية الأساسية لـ TanStack Query: القراءة من ذاكرة التخزين المؤقت أولاً → تصنيف البيانات على أنها حديثة أو منتهية الصلاحية → تشغيل طلب جديد تلقائيًا عند انتهاء صلاحيتها.
3. سيناريو واقعي
يجب أن تعرض لوحة التحكم الخاصة بـ«توم» العدد الإجمالي للمستخدمين، والعدد الإجمالي للطلبات، وقائمة بالطلبات الأخيرة. تأتي هذه البيانات من ثلاث واجهات برمجة تطبيقات (API) مختلفة، ويجب تحديثها في الوقت الفعلي — فعندما يقوم المستخدم بتعديل البيانات في صفحة أخرى ثم يعود إلى لوحة التحكم، يجب أن يرى أحدث النتائج. بالإضافة إلى ذلك، بعد إرسال نموذج «إضافة منتج»، يجب أن يتم تحديث قائمة المنتجات تلقائيًا، بدلاً من الحاجة إلى تحديث الصفحة يدويًّا.
(1) المشكلة: إدارة ذاكرة التخزين المؤقت يدويًّا أمرٌ صعبٌ للغاية
بدون TanStack Query، كان على توم أن يتولى الأمر بنفسه:
// Manually Manage the Cache — You have to write similar logic for each component.
function Dashboard() {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/stats').then(res => res.json())
.then(d => { setData(d); setLoading(false) })
.catch(e => { setLoading(false) })
}, [])
// Question 1:Switch to another page and then come back → Resubmit Request(Waste!)
// Question 2:If another component also needs the same data → Duplicate Request
// Question 3:The data does not refresh automatically,Users may be seeing data from a few minutes ago
}
يحل TanStack Query جميع المشكلات المذكورة أعلاه باستخدام ربط useQuery واحد.
| ميزة | الاسترداد اليدوي + useEffect | استعلام TanStack |
|---|---|---|
| إدارة ذاكرة التخزين المؤقت | لا توجد ذاكرة تخزين مؤقت؛ تُفقد البيانات عند الإزالة | التخزين المؤقت التلقائي؛ تتم إدارته بواسطة queryKey |
| طلب مكرر | عدة مكونات تستخدم نفس البيانات → طلبات مكررة | إزالة التكرارات تلقائيًا؛ تقديم الطلب مرة واحدة فقط |
| التحديث التلقائي | لا شيء | الاسترداد التلقائي عند التركيز على النافذة/إعادة الاتصال |
| تحديث الخلفية | لا شيء | تتحكم «staleTime» في تحديث الخلفية |
| حالة التحميل/الخطأ | التحميل/الخطأ المُدار يدويًّا | البيانات/التحميل/الخطأ المُقدَّمة تلقائيًّا |
| تحديث متفائل | التنفيذ اليدوي | onMutate + onError التراجع |
npm install @tanstack/react-query
(2) تهيئة المزود
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
// Create QueryClient(Usually at app Entrance)
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30 * 1000, // Default 30 Data within seconds is considered"Fresh"
cacheTime: 5 * 60 * 1000, // Cache Retention 5 minutes
retry: 3, // Failure retry 3 times
refetchOnWindowFocus: true, // Refresh when the window returns to the foreground
}
}
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
</QueryClientProvider>
)
}
4. useQuery: استرداد البيانات
▶ المثال 1: استرداد بيانات لوحة المعلومات
import { useQuery } from '@tanstack/react-query'
// Encapsulated API Function
async function fetchDashboardStats() {
const response = await fetch('/api/dashboard/stats')
if (!response.ok) throw new Error('Failed to retrieve statistics')
return response.json()
}
function Dashboard() {
// useQuery One line of code replaced useState + useEffect + Manual Caching
const {
data: stats, // Response Data
isLoading, // Loading for the first time(When there is no cache)
isFetching, // Is a request being made?(Including background retries)
error, // Error Object
refetch // Manually Trigger Retrieval
} = useQuery({
queryKey: ['dashboardStats'], // Unique Identifier,Used for caching matches
queryFn: fetchDashboardStats, // Data Retrieval Functions
staleTime: 30 * 1000, // 30 Do not resend the request within seconds
retry: 3, // Failure retry 3 times
})
if (isLoading) return <DashboardSkeleton />
if (error) return <ErrorPanel message={error.message} onRetry={refetch} />
return (
<div className="dashboard">
<StatCard title="Total Number of Users" value={stats.users} />
<StatCard title="Total Number of Orders" value={stats.orders} />
<StatCard title="Total Revenue" value={`$${stats.revenue}`} />
</div>
)
}
// The sidebar also displays the number of users — Using the same queryKey,No duplicate requests!
function Sidebar() {
const { data: stats } = useQuery({
queryKey: ['dashboardStats'],
queryFn: fetchDashboardStats,
staleTime: 30 * 1000,
})
return (
<aside>
<p>Users Online:{stats?.onlineUsers ?? '...'}</p>
</aside>
)
}
الآلية الأساسية: تتشارك مثيلات queryKey المتطابقة نفس ذاكرة التخزين المؤقت. تستخدم لوحة التحكم والشريط الجانبي نفس ['dashboardStats']، ويقوم TanStack Query تلقائيًا بإزالة التكرار من الطلبات — حيث لا يُطلق كل مكون سوى طلب API واحد فقط، ويتم تحديث كلا المكونين في آن واحد بمجرد إرجاع البيانات.
▶ المثال 2: الاستعلامات التي تحتوي على معلمات
function ProductDetail({ productId }) {
const { data, isLoading, error } = useQuery({
queryKey: ['product', productId], // queryKey Includes parameters
queryFn: async () => {
const res = await fetch(`/api/products/${productId}`)
if (!res.ok) throw new Error('The product does not exist.')
return res.json()
},
enabled: !!productId, // productId Do not send a request if it is empty
staleTime: 60 * 1000,
})
if (isLoading) return <p>Loading product details...</p>
if (error) return <p>Error:{error.message}</p>
return (
<div>
<h2>{data.name}</h2>
<p className="price">${data.price}</p>
<p>{data.description}</p>
</div>
)
}
أهمية المعلمات المضمنة في queryKey: يستخدم TanStack Query queryKey كمعرّف فريد للذاكرة المؤقتة. ['product', 1] و['product', 2] هما ذاكرتان مؤقتتان منفصلتان لا تتداخلان مع بعضهما البعض. عندما يتغير productId من 1 إلى 2، يعطي النظام الأولوية لقراءة ['product', 2] من الذاكرة المؤقتة؛ فإذا كانت مخزنة في الذاكرة المؤقتة ولم تنته صلاحيتها، يتم عرضها مباشرةً؛ وإلا، فإنه يرسل طلبًا.
▶ مثال: الحقول الرئيسية التي تُرجعها useQuery
| الحقل | المعنى | حالة الاستخدام |
|---|---|---|
data |
البيانات من آخر استجابة ناجحة | عرض واجهة المستخدم |
isLoading |
التحميل الأول بدون بيانات مخزنة في ذاكرة التخزين المؤقت | عرض الشاشة الأساسية عند التحميل الأول |
isFetching |
أي طلبات جارية (بما في ذلك محاولات إعادة الإرسال في الخلفية) | عرض مؤشر التحديث في الخلفية |
error |
كائن الخطأ الخاص بالطلب الفاشل | عرض رسالة الخطأ |
refetch |
وظيفة لتشغيل إعادة الطلب يدويًّا | زر «تحديث» |
isStale |
هل البيانات قديمة؟ | عرض مطالبات التحديث بشكل مشروط |
5. useMutation: كتابة البيانات
استخدم useQuery لعمليات القراءة وuseMutation لعمليات الكتابة. هذه هي القاعدة الذهبية في TanStack Query.
▶ المثال 3: إضافة منتج وتحديث القائمة
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
// Product List Search
function useProducts() {
return useQuery({
queryKey: ['products'],
queryFn: async () => {
const res = await fetch('/api/products')
return res.json()
}
})
}
function ProductManager() {
const queryClient = useQueryClient()
// Add a product mutation
const addProductMutation = useMutation({
mutationFn: async (newProduct) => {
const res = await fetch('/api/products', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newProduct)
})
if (!res.ok) throw new Error('Failed to add')
return res.json()
},
// Steps to Take After Success:Invalidate the product list cache,Trigger a re-request
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['products'] })
// Optional:Display a success message at the same time
alert('Item added successfully!')
},
// Error Handling
onError: (error) => {
alert(`Failed to add:${error.message}`)
}
})
// Data
const { data: products, isLoading } = useProducts()
// Form Submission
function handleSubmit(event) {
event.preventDefault()
const formData = new FormData(event.target)
const newProduct = {
name: formData.get('name'),
price: Number(formData.get('price'))
}
addProductMutation.mutate(newProduct)
event.target.reset()
}
return (
<div>
<h2>Product Management</h2>
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Product Name" required />
<input name="price" type="number" placeholder="Price" required />
<button type="submit" disabled={addProductMutation.isLoading}>
{addProductMutation.isLoading ? 'Submitting......' : 'Add Item'}
</button>
</form>
{addProductMutation.isError && (
<p className="error">Submission Failed:{addProductMutation.error.message}</p>
)}
<hr />
<h3>Product List</h3>
{isLoading && <p>Loading......</p>}
{products && (
<ul>
{products.map(p => (
<li key={p.id}>{p.name} — ${p.price}</li>
))}
</ul>
)}
</div>
)
}
العملية الأساسية: useMutation.mutate() يُطلق طلب POST → يقوم الخادم بمعالجة الطلب → onSuccess يُنفذ invalidateQueries في استدعاء الرد → يقوم TanStack Query تلقائيًا بإعادة استرداد بيانات ['products'] → يتم تحديث واجهة المستخدم الخاصة بالقائمة تلقائيًا.
لماذا لا تستخدم setData يدويًّا؟ يمكنك استدعاء queryClient.setQueryData لتحديث ذاكرة التخزين المؤقت يدويًّا، لكن الطريقة الأفضل هي جعل TanStack Query يعيد طلب البيانات من الخادم عبر invalidateQueries — فهذا يضمن أن البيانات تأتي دائمًا من الخادم ويمنع حدوث تباينات بين ذاكرة التخزين المؤقت في الواجهة الأمامية والبيانات الموجودة على جانب الخادم.
6. التحديثات المتفائلة
عندما يكون زمن انتقال الشبكة طويلاً، يضطر المستخدمون إلى انتظار استجابة الخادم بعد إرسال الطلب قبل أن يتمكنوا من رؤية التغييرات في واجهة المستخدم، مما يؤدي إلى تجربة مستخدم سيئة. أما التحديث المتفائل فيقوم بتحديث واجهة المستخدم فورًا قبل إرسال الطلب، ويعود إلى الحالة السابقة في حال فشل الطلب.
▶ المثال 4: تبديل حالة إتمام المهمة
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
// Get the To-Do List
function useTodos() {
return useQuery({
queryKey: ['todos'],
queryFn: async () => {
const res = await fetch('/api/todos')
return res.json()
}
})
}
function TodoList() {
const queryClient = useQueryClient()
const { data: todos } = useTodos()
// Switch to "Completed" status — Using Optimistic Updates
const toggleMutation = useMutation({
// Actual API Request
mutationFn: async ({ id, done }) => {
const res = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ done })
})
if (!res.ok) throw new Error('Update Failed')
return res.json()
},
// ===== Optimistic Update Begins =====
onMutate: async ({ id, done }) => {
// 1. Cancel all ongoing todos Search(Avoid Overwriting Optimistic Updates)
await queryClient.cancelQueries({ queryKey: ['todos'] })
// 2. Save the current cache data,Used for rollback in case of failure
const previousTodos = queryClient.getQueryData(['todos'])
// 3. Refresh the cache immediately
queryClient.setQueryData(['todos'], (old) =>
old.map(todo =>
todo.id === id ? { ...todo, done } : todo
)
)
// 4. Restore old data for rollback
return { previousTodos }
},
// ===== End of Optimistic Update =====
// Rollback on Failure
onError: (err, variables, context) => {
if (context?.previousTodos) {
queryClient.setQueryData(['todos'], context.previousTodos)
}
},
// Whether we succeed or fail,Finally, resend the request to ensure synchronization with the server.
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
}
})
return (
<ul>
{todos?.map(todo => (
<li key={todo.id} style={{ opacity: toggleMutation.isLoading ? 0.7 : 1 }}>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleMutation.mutate({ id: todo.id, done: !todo.done })}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.title}
</span>
</li>
))}
</ul>
)
}
دليل التفاؤل المكون من ثلاث خطوات:
onMutate: قم بتحديث ذاكرة التخزين المؤقت مباشرةً قبل إرسال الطلب حتى يتمكن المستخدمون من رؤية التغييرات على الفور؛ واحتفظ بالبيانات القديمة تحسبًا للحاجة إلى التراجع عن التغييرات.onError: استعادة ذاكرة التخزين المؤقت (الرجوع إلى الحالة السابقة) باستخدام البيانات التاريخية المحفوظة عند فشل الطلبonSettled: بغض النظر عن النجاح أو الفشل، يجب في النهاية طلب البيانات مرة أخرى من الخادم لضمان الاتساق التام.
7. الحالة من جانب الخادم مقابل الحالة من جانب العميل
لفهم المفهوم الكامن وراء TanStack Query، من الضروري التمييز بين حالتين:
| البعد | حالة الخادم | حالة العميل |
|---|---|---|
| المصدر | واجهة برمجة التطبيقات الخلفية / قاعدة البيانات | الواجهة الأمامية (المحلية، إجراءات المستخدم) |
| الملكية | الخادم هو صاحب البيانات | الواجهة الأمامية هي صاحبة البيانات |
| الاستمرارية | مخزّن في قاعدة بيانات | مخزّن في الذاكرة أو في localStorage |
| متطلبات التزامن | يجب أن يظل متزامنًا مع الخادم | لا يلزم أن يظل متزامنًا مع الخادم |
| طريقة التحديث | الكتابة عبر واجهة برمجة التطبيقات (API) + إعادة الاسترداد | استخدام setState مباشرةً |
| أدوات الإدارة | TanStack Query | State / Redux Toolkit |
| مثال | قائمة المستخدمين، بيانات المنتجات، معلومات الطلبات | زر التبديل المنبثق، قيم إدخال النماذج، لون السمة |
المبادئ الأساسية:
- البيانات التي تُرجعها واجهة برمجة التطبيقات (API) (قائمة المستخدمين، معلومات المنتج، حالة الطلب) → تُدار باستخدام TanStack Query
- الحالة المحلية للواجهة الأمامية (فتح/إغلاق النوافذ المنبثقة، قيم حقول الإدخال، إعدادات السمة) → إدارة هذه الحالة باستخدام Zustand / Context / useState
▶ مثال: استعلام TanStack في DevTools
يوفر TanStack Query مكونًا مخصصًا في DevTools يتيح لك عرض حالة ذاكرة التخزين المؤقت ووقت انتهاء الصلاحية ووقت آخر تحديث لجميع الاستعلامات أثناء مرحلة التطوير.
npm install @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
function App() {
return (
<QueryClientProvider client={queryClient}>
<YourApp />
{/* Display only in the development environment DevTools */}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
ميزات DevTools:
- عرض جميع «queryKeys» والبيانات المخزنة مؤقتًا المرتبطة بها
- عرض حالات «القديمة» و«النشطة» و«غير النشطة» لكل إدخال في ذاكرة التخزين المؤقت
- تشغيل عمليات إعادة الاسترداد وإبطال الصلاحية والإزالة يدويًّا
- مراقبة زمن انتقال الشبكة وحجم استجابة الطلبات
▶ مثال: الطرق العامة لـ QueryClient
بالإضافة إلى useQuery وuseMutation، يوفر الكائن queryClient أيضًا عدة طرق عامة للتعامل مع ذاكرة التخزين المؤقت من خارج المكون:
import { queryClient } from './queryClient'
// 1. Invalidate the cache(Trigger a re-request)
queryClient.invalidateQueries({ queryKey: ['products'] })
// 2. Clear All Cache(When a user logs out)
queryClient.clear()
// 3. Set the default option(Global Changes staleTime)
queryClient.setDefaultOptions({
queries: { staleTime: 60 * 1000 }
})
// 4. Retrieve Cached Data(Do not trigger a request)
const cachedData = queryClient.getQueryData(['products'])
// 5. Prefetch Data(User hover Preload when the link is reached)
queryClient.prefetchQuery({
queryKey: ['product', '42'],
queryFn: () => fetchProduct('42')
})
// After the user clicks the link,,The data is already in the cache.,Instant Rendering
// 6. Cancel the current query
queryClient.cancelQueries({ queryKey: ['product', '42'] })
الجلب المسبق للبيانات (prefetchQuery) هو وسيلة فعالة لتحسين تجربة المستخدم. فعندما يمرر المستخدم مؤشر الفأرة فوق رابط ما، يتم إرسال طلب مسبقًا بحيث تكون البيانات قد تم تحميلها بالفعل بحلول الوقت الذي ينقر فيه المستخدم، مما يحقق تأثير «التحميل الفوري».
▶ مثال: متى يجب تخصيص إعدادات QueryClient
يحدد defaultOptions الذي تم تعريفه أثناء التهيئة القيمة الافتراضية العامة، لكن كل استدعاء لـ useQuery يمكنه تجاوزها بشكل فردي:
// Global Default:30 Freshness Period (in Seconds)
const queryClient = new QueryClient({
defaultOptions: {
queries: { staleTime: 30 * 1000, retry: 3 }
}
})
// A Query Override:The data remains virtually unchanged,Set the shelf life to 10 minutes
function useProductCategories() {
return useQuery({
queryKey: ['categories'],
queryFn: fetchCategories,
staleTime: 10 * 60 * 1000, // 10 Do not retry within minutes
})
}
// Another query override:High real-time requirements,Disable Caching
function useRealtimeNotifications() {
return useQuery({
queryKey: ['notifications'],
queryFn: fetchNotifications,
staleTime: 0, // The data expires immediately
refetchInterval: 30 * 1000, // Auto-poll every 30s
})
}
مبادئ التكوين: قم بتعيين قيم متحفظة (فترة staleTime أقصر) في التكوين العام، وقم بتجاوزها بشكل فردي لكل استعلام بناءً على خصائص البيانات. بالنسبة للبيانات التي تتغير بشكل متكرر (الإشعارات، الإحصائيات في الوقت الفعلي)، قم بتعيين staleTime قصير أو حتى قم بتمكين الاستقصاء؛ أما بالنسبة للبيانات التي تتغير بشكل غير متكرر (قوائم الفئات، معلومات التكوين)، فقم بتعيين staleTime طويل لتقليل عدد الطلبات.
❓ أسئلة شائعة
useEffect + fetch؟queryKey، يتم تصفية التكرارات تلقائيًا لمنع الطلبات الزائدة؛ (2) إعادة الاسترجاع التلقائي — يتم تحديث البيانات تلقائيًا عند إعادة النافذة إلى المقدمة، أو عند إعادة الاتصال بالشبكة، أو أثناء الاستقصاء الدوري؛ (3) إدارة دورة الحياة — المعالجة التلقائية لحالات التحميل والأخطاء والبيانات؛ وإعادة المحاولة تلقائيًا عند الفشل؛ وعدم الحاجة إلى تنفيذ AbortController يدويًّا لإلغاء الطلبات. يستبدل useQuery واحد 20 سطرًا من useEffect + fetch + useState.staleTime وcacheTime؟ ماذا يحدث عند staleTime = 0؟staleTime في "حداثة" البيانات — خلال هذه الفترة الزمنية، تُعتبر البيانات حديثة ولن تؤدي إلى إعادة استرجاع تلقائي. يتحكم cacheTime في مدة الاحتفاظ بالذاكرة المؤقتة — المدة التي تبقى فيها الذاكرة المؤقتة بعد إلغاء تثبيت المكون قبل أن يتم جمعها كبيانات غير مطلوبة. staleTime = 0 يعني أن البيانات تُصنف على أنها منتهية الصلاحية فور إرجاعها، مما يؤدي إلى إعادة استرجاعها في الخلفية في كل مرة يتم استخدامها (على الرغم من أن البيانات المخزنة مؤقتًا تُرجع أولاً ثم يتم تحديثها). يُنصح بتعيين staleTime = 30 ثانية لتجنب تقلب الطلبات.onSuccess الخاصة بـ useMutation، كيف يمكنني الاختيار بين invalidateQueries وsetQueryData؟invalidateQueries — إبطال صلاحية ذاكرة التخزين المؤقت لإجبار TanStack Query على إعادة استرداد البيانات من الخادم، مما يضمن اتساق البيانات. تعد setQueryData مناسبة للسيناريوهات التي تتضمن تغييرات قليلة جدًا وتنسيق إرجاع معروف (مثل إنشاء معرّف فريد من جانب العميل). إذا كنت بحاجة إلى عرض نتائج إجراءات المستخدم على الفور (دون انتظار استجابة الخادم)، فاستخدم التحديثات المتفائلة (onMutate + rollback) بدلاً من setQueryData.queryKey، سيتم إرسال طلب واحد فقط.📖 ملخص
- تتولى
useQueryإدارة عملية استرجاع البيانات: التخزين المؤقت التلقائي، وإزالة التكرار من الطلبات، وإعادة الاسترجاع في الخلفية، وإعادة المحاولة في حالة الفشل - تقوم وظيفة
useMutationبإدارة عمليات كتابة البيانات: تعمل معinvalidateQueriesلتشغيل عمليات تحديث البيانات تلقائيًا - يتحكم
staleTimeفي حداثة البيانات، بينما يتحكمcacheTimeفي مدة الاحتفاظ بالبيانات المخزنة مؤقتًا - تحديثات محسّنة: استخدم
onMutateلتحديث واجهة المستخدم أولاً، وonErrorللرجوع إلى الإصدار السابق، وonSettledللمزامنة النهائية، مما يؤدي إلى تحسين تجربة المستخدم - استخدم TanStack Query للحالة من جانب الخادم (بيانات واجهة برمجة التطبيقات) وZustand/Context للحالة من جانب العميل (حالة واجهة المستخدم)
📝 تمارين
- قم بإنشاء مكون قائمة مستخدمين باستخدام
useQuery: استخدمhttps://jsonplaceholder.typicode.com/usersكواجهة برمجة تطبيقات (API)، وقم بتنفيذ مشاركة ذاكرة التخزين المؤقت (يستخدم كلا المكونين نفسqueryKeyلضمان إرسال الطلب مرة واحدة فقط)، وأضف زرًا للتحديث اليدوي. - استخدم
useMutationلتنفيذ ميزة «إضافة منتج»: عند إرسال النموذج، يتم إرسال طلب POST؛ وفي حالة النجاح، يتم تحديث قائمة المنتجات تلقائيًا؛ أما في حالة الفشل، فتظهر رسالة خطأ. - قم بتنفيذ تحديث متفائل لـ «تبديل حالة إتمام مهمة ما»: يؤدي النقر على مربع الاختيار إلى تبديل الحالة على الفور؛ وإذا فشل طلب واجهة برمجة التطبيقات (API)، يتم التراجع عن التغيير. بعد كتابة الكود، اختبره عن طريق قطع الاتصال بالشبكة والنقر على مربع الاختيار لمراقبة سلوك التراجع.