מבוא
מפתחי React כותבים כל הזמן קוד תבניתי לטיפול בנתונים: useEffect, useState, reducers. כל מסך חדש משכפל את הלוגיקה של טעינה, שמירה במטמון ועדכון. כתוצאה מכך, 60–70% מהקוד הוא קוד תבניתי שמסיח את הדעת מהלוגיקה העסקית. בפרויקט אחד, החלפנו את Redux Thunk ב-TanStack Query, קיצצנו את הקוד ב-70%, והפחתנו באגים פי שלושה.
TanStack Query (לשעבר React Query) היא ספרייה לניהול state של השרת. היא מטפלת במטמון, עדכונים ברקע, דדופליקציה של בקשות, פאג'ינציה, מוטציות עם עדכונים אופטימיים, וטעינה מוקדמת. התוצאה: קוד שקשור לנתונים קטן ב-60–70%, ובאגים יורדים פי 2–3. אנו מגדירים את TanStack Query מאז ימיה הראשונים, עם למעלה מ-50 פרויקטים שהושלמו. אנו מבטיחים פעולה יציבה וחיסכון של 30–50% בזמן. צרו קשר לקבלת הגדרה וקבלו ארכיטקטורת נתונים מלאה תוך ימים.
| גישה | קוד תבניתי | מורכבות | מדרגיות |
|---|---|---|---|
| Redux Thunk | גבוה | גבוהה | בינונית |
| RTK Query | בינוני | בינונית | גבוהה |
| TanStack Query | נמוך | נמוכה | גבוהה |
| SWR | נמוך | נמוכה | בינונית |
בעיות ש-TanStack Query פותר
ניהול מטמון ידני: ללא הספרייה, צריך לסנכרן בקשות ידנית, לטפל בטעינה ובשגיאות. TanStack Query שומרת נתונים במטמון אוטומטית, מבצעת דדופליקציה של בקשות מקבילות, ומעדכנת את המטמון במוטציות. נקודת כאב נוספת היא פסילת מטמון. עם מפתחות היררכיים, ניתן לפסול את כל הרשימות הקשורות בפקודה אחת מבלי לגעת בפרטים.
איך להגדיר QueryClient עם פרמטרים אופטימליים
reducers הוא האובייקט המרכזי שמנהל את כל השאילתות. התצורה שלו משפיעה על הביצועים. הנה ערכים מומלצים:
| פרמטר | ברירת מחדל | מומלץ | למה |
|---|---|---|---|
staleTime |
0 | 5 * 60 * 1000 | נתונים נחשבים טריים למשך 5 דקות; אין טעינה מחדש בכל mount |
gcTime |
5 * 60 * 1000 | 10 * 60 * 1000 | המטמון נשמר 10 דקות לאחר הסרת הפניות (לשעבר cacheTime) |
retry |
3 | 2 | ניסיונות חוזרים בשגיאה – מספיק לכשלים זמניים |
refetchOnWindowFocus |
false | true | עדכון נתונים אוטומטי בחזרה לטאב |
דוגמה לאתחול בסיסי:
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
gcTime: 10 * 60 * 1000,
retry: 2,
refetchOnWindowFocus: true,
},
mutations: {
retry: 0,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<Router />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}אם הפרויקט משתמש ב-Next.js App Router, אנו מוסיפים טעינה מוקדמת בצד השרת באמצעות import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, retry: 2, refetchOnWindowFocus: true, }, mutations: { retry: 0, }, }, }) function App() { return ( <QueryClientProvider client={queryClient}> <Router /> <ReactQueryDevtools initialIsOpen={false} /> </QueryClientProvider> ) } ו-dehydrate.
למה מפתחות שאילתה הם המפתח לפסילה יעילה
מפתחות שאילתה הם מזהים ייחודיים לשאילתה. מבנה המפתח קובע כיצד המטמון נפסל לאחר מוטציות. אנו משתמשים בגישה היררכית:
export const queryKeys = {
products: {
all: ['products'] as const,
lists: () => [...queryKeys.products.all, 'list'] as const,
list: (filters: ProductFilters) => [...queryKeys.products.lists(), filters] as const,
details: () => [...queryKeys.products.all, 'detail'] as const,
detail: (id: string) => [...queryKeys.products.details(), id] as const,
},
users: {
all: ['users'] as const,
me: () => [...queryKeys.users.all, 'me'] as const,
profile: (id: string) => [...queryKeys.users.all, 'profile', id] as const,
},
}זה מאפשר לפסול את כל רשימות המוצרים בפקודה אחת מבלי לגעת בפרטים.
ה-Hooks המרכזיים: useQuery, useMutation, useInfiniteQuery
ה-hook הבסיסי לטעינת נתונים הוא HydrationBoundary. הוא מחזיר מצב טעינה, שגיאה ונתונים. עבור UI קריטי, השתמשו ב-export const queryKeys = { products: { all: ['products'] as const, lists: () => [...queryKeys.products.all, 'list'] as const, list: (filters: ProductFilters) => [...queryKeys.products.lists(), filters] as const, details: () => [...queryKeys.products.all, 'detail'] as const, detail: (id: string) => [...queryKeys.products.details(), id] as const, }, users: { all: ['users'] as const, me: () => [...queryKeys.users.all, 'me'] as const, profile: (id: string) => [...queryKeys.users.all, 'profile', id] as const, }, } עם React Suspense.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
export function useProducts(filters: ProductFilters) {
return useQuery({
queryKey: queryKeys.products.list(filters),
queryFn: () => api.get<Product[]>('/products', { params: filters }),
placeholderData: (prev) => prev,
})
}
export function useUpdateProduct() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateProductDto }) =>
api.patch<Product>(`/products/${id}`, data),
onMutate: async ({ id, data }) => {
await queryClient.cancelQueries({ queryKey: queryKeys.products.detail(id) })
const previous = queryClient.getQueryData<Product>(queryKeys.products.detail(id))
queryClient.setQueryData(queryKeys.products.detail(id), (old: Product) => ({ ...old, ...data }))
return { previous }
},
onError: (_, { id }, context) => {
if (context?.previous) {
queryClient.setQueryData(queryKeys.products.detail(id), context.previous)
}
},
onSettled: (_, __, { id }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.products.detail(id) })
},
})
}לגלילה אינסופית – useQuery:
export function useInfiniteProducts(filters: Omit<ProductFilters, 'page'>) {
return useInfiniteQuery({
queryKey: queryKeys.products.list(filters),
queryFn: ({ pageParam }) =>
api.get<PaginatedResponse<Product>>('/products', {
params: { ...filters, page: pageParam, pageSize: 20 },
}),
initialPageParam: 1,
getNextPageParam: (lastPage) =>
lastPage.hasNextPage ? lastPage.page + 1 : undefined,
});
}לפאג'ינציה פשוטה, השתמשו ב-useSuspenseQuery בתוך import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' export function useProducts(filters: ProductFilters) { return useQuery({ queryKey: queryKeys.products.list(filters), queryFn: () => api.get<Product[]>('/products', { params: filters }), placeholderData: (prev) => prev, }) } export function useUpdateProduct() { const queryClient = useQueryClient() return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateProductDto }) => api.patch<Product>(`/products/${id}`, data), onMutate: async ({ id, data }) => { await queryClient.cancelQueries({ queryKey: queryKeys.products.detail(id) }) const previous = queryClient.getQueryData<Product>(queryKeys.products.detail(id)) queryClient.setQueryData(queryKeys.products.detail(id), (old: Product) => ({ ...old, ...data })) return { previous } }, onError: (_, { id }, context) => { if (context?.previous) { queryClient.setQueryData(queryKeys.products.detail(id), context.previous) } }, onSettled: (_, __, { id }) => { queryClient.invalidateQueries({ queryKey: queryKeys.products.detail(id) }) }, }) } .
איך ליישם עדכונים אופטימיים ללא כאב
עדכון אופטימי: ה-UI משתנה לפני שהשרת מגיב. TanStack Query עושה זאת בקלות: ב-useInfiniteQuery עדכנו את המטמון, ב-export function useInfiniteProducts(filters: Omit<ProductFilters, 'page'>) { return useInfiniteQuery({ queryKey: queryKeys.products.list(filters), queryFn: ({ pageParam }) => api.get<PaginatedResponse<Product>>('/products', { params: { ...filters, page: pageParam, pageSize: 20 } }), initialPageParam: 1, getNextPageParam: (lastPage) => (lastPage.hasNextPage ? lastPage.page + 1 : undefined), }) } בצעו rollback. הדוגמה למעלה עם keepPreviousData מציגה טכניקה זו. המשתמש רואה משוב מיידי, ובשגיאה הנתונים חוזרים למצבם המקורי.
טעינה מוקדמת ו-SSR עם Next.js
טעינה מוקדמת טוענת נתונים לפני ניווט לעמוד. לדוגמה, ב-hover על קישור:
function ProductLink({ id }: { id: string }) {
const queryClient = useQueryClient()
return (
<Link
to={`/products/${id}`}
onMouseEnter={() => {
queryClient.prefetchQuery({
queryKey: queryKeys.products.detail(id),
queryFn: () => api.get<Product>(`/products/${id}`),
staleTime: 60_000,
})
}}
>
Перейти
</Link>
)
}עבור SSR ב-Next.js App Router, השתמשו ב-placeholderData ו-onMutate בצד השרת:
import { HydrationBoundary, QueryClient, dehydrate } from '@tanstack/react-query'
export default async function ProductsPage() {
const queryClient = new QueryClient()
await queryClient.prefetchQuery({
queryKey: queryKeys.products.lists(),
queryFn: () => fetchProductsServer(),
})
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<ProductList />
</HydrationBoundary>
)
}פרטים נוספים בתיעוד הרשמי של TanStack Query.
מה כלול בשירות שלנו
אנו מגדירים את TanStack Query לפרויקט שלכם:
- התקנה והגדרת QueryClient המותאמת ל-API שלכם.
- עיצוב מבנה מפתחות שאילתה היררכי לפסילה קלה.
- כתיבת hooks מותאמים לכל ה-endpoints: useQuery, useMutation, useInfiniteQuery.
- יישום עדכונים אופטימיים לטפסים (לייקים, תגובות, עריכה).
- הגדרת טעינה מוקדמת ואינטגרציה עם SSR/Next.js.
- חיבור DevTools לניפוי באגים.
- תיעוד והדרכת צוות.
תהליך:
- ניתוח – ביקורת על הסטאק הנוכחי וה-API, זיהוי נקודות אינטגרציה.
- עיצוב – תכנית מפתחות שאילתה, בחירת אסטרטגיית מטמון.
- יישום – כתיבת hooks, בדיקת מוטציות, אופטימיזציה.
- בדיקות – אימות פסילה, הרשאות, מקרי קצה.
- פריסה – השקה, ניטור באמצעות DevTools.
לוח זמנים: בין 2 ל-5 ימים תלוי במספר ה-endpoints. העלות מחושבת באופן אישי. החיסכון בתחזוקה עתידית מחזיר את ההשקעה תוך שבועות.
צרו קשר לייעוץ – נעזור לכם ליישם את TanStack Query ולקצץ את זמן הפיתוח ב-30–50%. קבלו ארכיטקטורת נתונים מוכנה תוך ימים.







