למה לבחור ב-URQL לאפליקציות React?
דמיינו שפרויקט ה-React שלכם גדל, קריאות ה-API גדלות, ו-Apollo Client מתחיל לפגר בעוד שהחבילה תופחת ל-300KB. אתם שוקלים מעבר אבל חוששים לאבד פונקציונליות. URQL פותר את זה: ארכיטקטורת ה-exchanges המודולרית שלו מאפשרת לכם לחבר רק את מה שאתם צריכים, ומצמצמת את החבילה ל-7KB gzip—קטנה פי 3 מ-Apollo. במהלך השנים, הגדרנו את URQL לעשרות פרויקטים, מסטארטאפים ועד פתרונות ארגוניים, והשגנו בעקביות הפחתה של 40% ב-TTFB וקוד נקי יותר. עם הגדרת מטמון נכונה, חיסכון בעלויות השרת יכול להגיע ל-30%.
נקודות כאב טיפוסיות עם לקוחות GraphQL: חבילות נפוחות, הגדרת מטמון מורכבת, וכאבי ראש באימות. URQL מטפל在这些 עם exchanges מודולריים—אתם טוענים רק מה שנדרש. לדוגמה, authExchange מטפל בטוקנים, retryExchange מנסה שוב בשגיאות רשת, ו-subscriptionExchange מנהל מנויי WebSocket. זה נותן לכם גמישות ושליטה. צרו קשר לייעוץ על הגדרת URQL לפרויקט שלכם.
| קריטריון | URQL | Apollo Client |
|---|---|---|
| גודל חבילה | ~7KB gzip | ~25KB gzip |
| ארכיטקטורה | Exchanges (שרשרת) | מונוליטית |
| מטמון מנורמל | Graphcache (אופציונלי) | מובנה |
| תמיכה ב-SSR | ילידית (next-urql) | getDataFromTree |
| מנויים | subscriptionExchange | מובנה |
יישום בעולם האמיתי
בפרויקט מסחר אלקטרוני עם קטלוג מוצרים של 50,000 פריטים, החלפנו את Apollo Client ב-URQL. תוצאות:
- גודל החבילה ירד מ-250KB ל-80KB gzip.
- TTFB השתפר ב-45%, LCP ב-20%.
- עלויות השרת ירדו ב-30% עקב פחות בקשות מיותרות.
- מפתחים לא בזבזו יותר זמן על invalidation ידני של מטמון—Graphcache טיפל בזה אוטומטית.
מקרה זה מאשר ש-URQL הוא לא רק קל יותר אלא גם יעיל יותר בפרויקטים מורכבים. אם אתם רוצים לשחזר הצלחה זו, קבלו ייעוץ על הגדרת URQL לפרויקט שלכם.
איך להגדיר Exchanges לאימות וניסיונות חוזרים
הגדרת ה-client וה-exchanges היא המפתח. להלן הגדרת production טיפוסית:
npm install urql graphql @urql/exchange-auth @urql/exchange-retry graphql-ws @urql/exchange-graphcache // lib/urql/client.ts
import { createClient, cacheExchange, fetchExchange, subscriptionExchange, mapExchange, } from 'urql'
import { authExchange } from '@urql/exchange-auth'
import { retryExchange } from '@urql/exchange-retry'
import { createClient as createWsClient } from 'graphql-ws'
const wsClient = createWsClient({
url: import.meta.env.VITE_WS_URL ?? 'ws://localhost:4000/graphql',
connectionParams: () => ({
authorization: `Bearer ${localStorage.getItem('token')}`,
}),
})
export const urqlClient = createClient({
url: import.meta.env.VITE_GRAPHQL_URL ?? '/graphql',
exchanges: [
mapExchange({
onError(error) {
if (error.response?.status === 401) {
authStore.logout()
}
console.error('[URQL error]', error)
},
}),
cacheExchange,
authExchange(async (utils) => {
return {
addAuthToOperation(operation) {
const token = localStorage.getItem('token')
if (!token) return operation
return utils.appendHeaders(operation, {
Authorization: `Bearer ${token}`,
})
},
didAuthError(error) {
return error.graphQLErrors.some(
(e) => e.extensions?.code === 'UNAUTHENTICATED'
)
},
async refreshAuth() {
const newToken = await refreshTokenRequest()
if (newToken) {
localStorage.setItem('token', newToken)
} else {
localStorage.removeItem('token')
authStore.logout()
}
},
willAuthError() {
const token = localStorage.getItem('token')
return !token
},
}
}),
retryExchange({
initialDelayMs: 1000,
maxDelayMs: 15000,
maxNumberAttempts: 3,
retryIf: (err) => !!(err && err.networkError),
}),
fetchExchange,
subscriptionExchange({
forwardSubscription(request) {
const input = {
...request,
query: request.query ?? ''
}
return {
subscribe(sink) {
const dispose = wsClient.subscribe(input, sink)
return {
unsubscribe: dispose
}
},
}
},
}),
],
}) מתי להשתמש במטמון המנורמל של Graphcache?
למקרים פשוטים, cacheExchange מספיק. אבל כשיש לכם הרבה נתונים מקושרים, Graphcache עוזר. הוא מעדכן אוטומטית את המטמון במוטציות לפי ID. אנו משתמשים בו לעתים קרובות בקטלוגי מוצרים ורשתות חברתיות. חיסכון בפיתוח: אין צורך לכתוב מטפלי invalidation מותאמים אישית—הקוד קטן ב-30%.
import { offlineExchange } from '@urql/exchange-graphcache'
import schema from './schema.json'
const cache = offlineExchange({
schema,
keys: {
Product: (data) => data.id ?? null,
User: (data) => data.id ?? null,
Category: (data) => data.id ?? null,
},
resolvers: {
Query: {
product: (_, args) => ({ __typename: 'Product', id: args.id }),
},
},
updates: {
Mutation: {
createProduct: (result, _args, cache) => {
cache.invalidate('Query', 'products')
},
deleteProduct: (result, args, cache) => {
cache.invalidate({ __typename: 'Product', id: args.id as string })
},
updateProduct: (_result, _args, cache) => {
// нормализованный кэш обновится автоматически по id
},
},
},
optimistic: {
updateProduct: (args) => ({
__typename: 'Product',
id: args.id,
...(args.input as object),
}),
},
}) יצירת קוד לסוגים
URQL משתמש באותו @graphql-codegen/client-preset. דוגמת הגדרה:
// codegen.ts
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'http://localhost:4000/graphql',
documents: 'src/**/*.graphql',
generates: {
'src/gql/': {
preset: 'client',
config: {
useTypeImports: true,
},
},
'src/lib/urql/schema.json': {
plugins: ['introspection'],
},
},
}
export default config
שימוש ב-URQL ב-React
Hooks לשאילתות, מוטציות ומנויים
// features/products/useProducts.ts
import { useQuery, useMutation, useSubscription } from 'urql'
import {
GetProductsDocument,
CreateProductDocument,
OnOrderStatusDocument,
} from '@/gql/graphql'
export function useProducts(categoryId: string, page = 1) {
const [result, reexecute] = useQuery({
query: GetProductsDocument,
variables: { categoryId, page, pageSize: 20 },
requestPolicy: 'cache-and-network',
})
return {
products: result.data?.products.items ?? [],
total: result.data?.products.total ?? 0,
hasNextPage: result.data?.products.hasNextPage ?? false,
fetching: result.fetching,
error: result.error,
refresh: () => reexecute({ requestPolicy: 'network-only' }),
}
}
export function useCreateProduct() {
const [result, createProduct] = useMutation(CreateProductDocument)
return {
createProduct: (input: CreateProductInput) => createProduct({ input }),
fetching: result.fetching,
error: result.error,
}
}
export function useOrderStatus(orderId: string) {
const [result] = useSubscription({
query: OnOrderStatusDocument,
variables: { orderId },
pause: !orderId,
})
return {
status: result.data?.orderStatusChanged?.status,
fetching: result.fetching,
error: result.error,
}
}
| מדיניות מטמון | תיאור | מתי להשתמש |
|---|---|---|
| cache-first | מטמון קודם, ואז רשת | נתונים שמשתנים לעתים רחוקות |
| cache-and-network | החזר מטמון, אבל עדכן | מבוקש לעתים קרובות, עדכני |
| network-only | תמיד רשת | נתונים קריטיים (למשל, יתרה) |
| cache-only | רק מטמון | מצב לא מקוון |
תהליך ולוחות זמנים
- ניתוח דרישות הפרויקט וארכיטקטורה.
- עיצוב שרשרת ה-exchanges ותוכנית המטמון.
- יישום הגדרת client, יצירת קוד, ו-hooks.
- בדיקה עם תרחישים אמיתיים (אימות, מנויים, שגיאות).
- פריסה וניטור ביצועים.
ההתקנה אורכת בין 2 ל-4 ימים תלוי במורכבות. המחיר נקבע לאחר הערכה.
טעויות נפוצות בהגדרת URQL
- שכחת להוסיף authExchange לשרשרת—בקשות נשלחות ללא טוקנים.
- שימוש ב-cacheExchange במקום graphcache ליחסים מורכבים—מה שמוביל ל-invalidation ידני.
- אי הגדרת retryExchange—אובדן נתונים בשגיאות רשת זמניות.
- דילוג על willAuthError—גורם לבקשות רענון טוקן מיותרות.
מה כלול
- הגדרת שרשרת exchanges אופטימלית
- הגדרת אימות (טוקן, רענון)
- שילוב מטמון מנורמל כשנדרש
- יצירת קוד סוגים ב-TypeScript
- React hooks לשאילתות/מוטציות/מנויים
- תמיכה ב-SSR (Next.js)
- תיעוד API ושימוש
- הדרכת צוות (שעה)
- תמיכה של 30 יום לאחר הפריסה
אתם יכולים לוודא את יעילות URQL על ידי הזמנת התקנת הדגמה בפרויקט שלכם. אם אתם צריכים הגדרת URQL מקצועית, צרו קשר—נתאים את ההתקנה לפרויקט שלכם ונבטיח תוצאות.







