React: TanStack Query (React Query)

آخر تحديث: 2026-08-26

في المحاضرة السابقة، استخدم توم «الـhooks» المخصصة و«axios» لتغليف طلبات HTTP، لكن ظهرت مشكلات جديدة: فكل من لوحة التحكم والشريط الجانبي يعرضان عدد المستخدمين، ويقوم كل مكون بإرسال طلبه الخاص (مما يؤدي إلى إهدار النطاق الترددي وموارد الخادم)؛ وعندما يغير المستخدم اسم المستخدم الخاص به في الصفحة «أ»، تظل البيانات في الصفحة «ب» قديمة؛ وبعد إرسال نموذج التعديل، يتعين على المستخدم تشغيل عملية تحديث البيانات يدويًّا. وأدرك توم أنه: هناك حاجة إلى حل «إدارة الحالة من جانب الخادم» لمعاملة بيانات واجهة برمجة التطبيقات (API) كنوع خاص من الحالات — حالة يتم تخزينها مؤقتًا، ولها مدة صلاحية، ويمكن مزامنتها تلقائيًا.


1. ما ستتعلمه



2. الرسوم التخطيطية المفاهيمية

100%
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، كان على توم أن يتولى الأمر بنفسه:

JSX
// 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 التراجع
BASH
npm install @tanstack/react-query

(2) تهيئة المزود

JSX
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: استرداد بيانات لوحة المعلومات

JSX
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: الاستعلامات التي تحتوي على معلمات

JSX
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: إضافة منتج وتحديث القائمة

JSX
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: تبديل حالة إتمام المهمة

JSX
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>
  )
}

دليل التفاؤل المكون من ثلاث خطوات:

  1. onMutate: قم بتحديث ذاكرة التخزين المؤقت مباشرةً قبل إرسال الطلب حتى يتمكن المستخدمون من رؤية التغييرات على الفور؛ واحتفظ بالبيانات القديمة تحسبًا للحاجة إلى التراجع عن التغييرات.
  2. onError: استعادة ذاكرة التخزين المؤقت (الرجوع إلى الحالة السابقة) باستخدام البيانات التاريخية المحفوظة عند فشل الطلب
  3. onSettled: بغض النظر عن النجاح أو الفشل، يجب في النهاية طلب البيانات مرة أخرى من الخادم لضمان الاتساق التام.


7. الحالة من جانب الخادم مقابل الحالة من جانب العميل

لفهم المفهوم الكامن وراء TanStack Query، من الضروري التمييز بين حالتين:

البعد حالة الخادم حالة العميل
المصدر واجهة برمجة التطبيقات الخلفية / قاعدة البيانات الواجهة الأمامية (المحلية، إجراءات المستخدم)
الملكية الخادم هو صاحب البيانات الواجهة الأمامية هي صاحبة البيانات
الاستمرارية مخزّن في قاعدة بيانات مخزّن في الذاكرة أو في localStorage
متطلبات التزامن يجب أن يظل متزامنًا مع الخادم لا يلزم أن يظل متزامنًا مع الخادم
طريقة التحديث الكتابة عبر واجهة برمجة التطبيقات (API) + إعادة الاسترداد استخدام setState مباشرةً
أدوات الإدارة TanStack Query State / Redux Toolkit
مثال قائمة المستخدمين، بيانات المنتجات، معلومات الطلبات زر التبديل المنبثق، قيم إدخال النماذج، لون السمة

المبادئ الأساسية:

▶ مثال: استعلام TanStack في DevTools

يوفر TanStack Query مكونًا مخصصًا في DevTools يتيح لك عرض حالة ذاكرة التخزين المؤقت ووقت انتهاء الصلاحية ووقت آخر تحديث لجميع الاستعلامات أثناء مرحلة التطوير.

BASH
npm install @tanstack/react-query-devtools
JSX
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:

  1. عرض جميع «queryKeys» والبيانات المخزنة مؤقتًا المرتبطة بها
  2. عرض حالات «القديمة» و«النشطة» و«غير النشطة» لكل إدخال في ذاكرة التخزين المؤقت
  3. تشغيل عمليات إعادة الاسترداد وإبطال الصلاحية والإزالة يدويًّا
  4. مراقبة زمن انتقال الشبكة وحجم استجابة الطلبات

▶ مثال: الطرق العامة لـ QueryClient

بالإضافة إلى useQuery وuseMutation، يوفر الكائن queryClient أيضًا عدة طرق عامة للتعامل مع ذاكرة التخزين المؤقت من خارج المكون:

JSX
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 يمكنه تجاوزها بشكل فردي:

JSX
// 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 طويل لتقليل عدد الطلبات.



❓ أسئلة شائعة

س ما هي المزايا الرئيسية لـ TanStack Query مقارنةً باستخدام useEffect + fetch؟
ج ثلاث مزايا رئيسية: (1) التخزين المؤقت المشترك — عندما تستخدم مكونات متعددة نفس 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)، كيف يحدد TanStack Query متى يجب إطلاق طلب جديد؟
ج القواعد هي: (1) إذا تم العثور على النتيجة في ذاكرة التخزين المؤقت (cache hit) ولم تنته صلاحية البيانات (ضمن فترة staleTime)، يتم إرجاع البيانات المخزنة مؤقتًا مباشرةً دون إرسال طلب؛ (2) إذا تم العثور على البيانات في ذاكرة التخزين المؤقت ولكن صلاحيتها انتهت، يتم إرجاع النتيجة المخزنة على الفور، ويتم بدء طلب جديد في الخلفية؛ (3) إذا لم تكن هناك ذاكرة تخزين مؤقت، يتم بدء طلب. وبغض النظر عن عدد المكونات المشتركة في نفس queryKey، سيتم إرسال طلب واحد فقط.
س هل يمكن استخدام TanStack Query وZustand معًا؟
ج نعم، ويُوصى باتباع هذا النهج. حيث يتولى TanStack Query إدارة الحالة من جانب الخادم (بيانات واجهة برمجة التطبيقات)، بينما يتولى Zustand إدارة الحالة من جانب العميل (حالة واجهة المستخدم). بنية شائعة: يقوم TanStack Query بجلب البيانات وتخزينها مؤقتًا → ثم يضخ البيانات إلى مخزن Zustand لمزيد من المعالجة → تقرأ المكونات الحالة النهائية من Zustand. وهما متكاملان، ولا يحل أحدهما محل الآخر.

📖 ملخص


📝 تمارين

  1. قم بإنشاء مكون قائمة مستخدمين باستخدام useQuery: استخدم https://jsonplaceholder.typicode.com/users كواجهة برمجة تطبيقات (API)، وقم بتنفيذ مشاركة ذاكرة التخزين المؤقت (يستخدم كلا المكونين نفس queryKey لضمان إرسال الطلب مرة واحدة فقط)، وأضف زرًا للتحديث اليدوي.
  2. استخدم useMutation لتنفيذ ميزة «إضافة منتج»: عند إرسال النموذج، يتم إرسال طلب POST؛ وفي حالة النجاح، يتم تحديث قائمة المنتجات تلقائيًا؛ أما في حالة الفشل، فتظهر رسالة خطأ.
  3. قم بتنفيذ تحديث متفائل لـ «تبديل حالة إتمام مهمة ما»: يؤدي النقر على مربع الاختيار إلى تبديل الحالة على الفور؛ وإذا فشل طلب واجهة برمجة التطبيقات (API)، يتم التراجع عن التغيير. بعد كتابة الكود، اختبره عن طريق قطع الاتصال بالشبكة والنقر على مربع الاختيار لمراقبة سلوك التراجع.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%