استرجاع البيانات: useFetch
عندما فتحت Alice صفحة MegaShop الرئيسية، رأت صفحة فارغة—لم تُحضر بيانات المنتجات حتى أجرى العميل طلبًا، واستغرق التصيير 3 ثوانٍ. عرف Charlie أن useFetch في Nuxt 3 يمكنه تحميل البيانات مسبقًا على جانب الخادم، مما يسمح لـ HTML بتضمين معلومات المنتج مباشرة ليكون المحتوى مرئيًا فورًا على الشاشة الأولى.
1. ما ستتعلمه
- useFetch / useAsyncData: العملية الكاملة للجلب المسبق لـ SSR والإماهة من جانب العميل
- الفروقات بين طلب $fetch الخام وطلب useFetch المُغلّف
- خيارات الطلب: server/lazy/immediate/dedupe/transform/pick
- التحديث والاستقصاء: refresh() / وضع watch
- قائمة منتجات MegaShop: الجلب المسبق لـ SSR في الممارسة
2. قصة حقيقية من مستهلكة
(1) نقطة الألم: شاشة فارغة لمدة 3 ثوانٍ
فتحت Alice موقع MegaShop على هاتفها ورأت شاشة فارغة مع أيقودة دوران لمدة 3 ثوانٍ قبل ظهور المنتجات. كان الأمر أسوأ في المتصفح على المترو باتصال شبكة ضعيف—شاشة فارغة لمدة 5 ثوانٍ. حلل Charlie الموقف ووجد أن بيانات المنتجات لا تُحضر حتى ينتهي تحميل JavaScript من جانب العميل. عملية التصيير بالكامل تتبع هذا التسلسل: تحميل JavaScript ← التنفيذ ← الجلب ← التصيير—وهو أمر بطيء للغاية.
(2) حل الجلب المسبق لـ SSR باستخدام useFetch
يقوم useFetch بجلب البيانات أثناء مرحلة SSR، ويضمّن HTML محتوى المنتج مباشرة:
<script setup lang="ts">
// الخادم: جلب البيانات أثناء SSR
// العميل: استخدام البيانات المُحضّرة مسبقًا، بدون طلب مكرر
const { data: products } = await useFetch('/api/products')
</script>
(3) النتائج: انخفاض وقت تحميل الشاشة الأولى من 3 ثوانٍ إلى 0.8 ثانية
بعد التحميل المسبق لـ SSR، ترى Alice المنتج فورًا عند فتح الصفحة؛ انخفض LCP من 3 ثوانٍ إلى 0.8 ثانية، وتحسن تقييم تجربة المستخدم من 52 إلى 94.
3. useFetch و useAsyncData
(1) الجدول الزمني للجلب المسبق لبيانات SSR حتى إماهة العميل
sequenceDiagram
participant B as Browser
participant S as Nuxt Server
participant A as API Server
B->>S: GET /products
S->>A: fetch /api/products
A-->>S: JSON data
S->>S: Render HTML with data
S-->>B: HTML + payload (data embedded)
B->>B: Display HTML (instant first paint)
B->>B: Hydrate with payload (no re-fetch)
(2) مقارنة useFetch و useAsyncData
| البُعد | useFetch | useAsyncData |
|---|---|---|
| الغرض | غلاف لـ $fetch | استرجاع بيانات عام |
| طريقة الطلب | $fetch مدمج | معالج مخصص |
| استجابة URL | ✅ إعادة طلب تلقائية عند تغير URL | ❌ يجب المراقبة يدويًا |
| استنتاج النوع | ✅ استنتاج تلقائي لنوع الاستجابة | ⚠️ يجب تحديد الأنواع العامة يدويًا |
| حالة الاستخدام | معظم طلبات API | طلبات غير قياسية/منطق معقد |
(1) ▶ مثال: الاستخدام الأساسي لـ useFetch
<!-- pages/products/index.vue -->
<template>
<div>
<h1>Products</h1>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Failed to load products</div>
<div v-else>
<ProductCard v-for="p in products" :key="p.id" :product="p" />
</div>
</div>
</template>
<script setup lang="ts">
const { data: products, pending, error } = await useFetch('/api/products')
</script>
الناتج:
// Execution Successful
(2) ▶ مثال: معالج مخصص لـ useAsyncData
<script setup lang="ts">
const { data: stats } = await useAsyncData('product-stats', async () => {
// منطق جلب بيانات مخصص
const [total, featured, onSale] = await Promise.all([
$fetch('/api/products/count'),
$fetch('/api/products/featured'),
$fetch('/api/products/on-sale')
])
return { total, featured, onSale }
})
</script>
الناتج:
// Execution Successful
4. $fetch مقابل useFetch
(1) مقارنة الفروقات الرئيسية
| البُعد | $fetch | useFetch |
|---|---|---|
| تمرير بيانات SSR | ❌ بدون تمرير | ✅ تمرير payload تلقائي |
| تفاعلية | ❌ بيانات خام | ✅ ref تفاعلية |
| إدارة الحالة | ❌ لا يوجد | ✅ pending/error/refresh |
| طلب مكرر | ❌可能 تكرار | ✅ إلغاء التكرار |
| حالات الاستخدام | معالجة الأحداث/مسار API | استرجاع بيانات الصفحة |
(1) ▶ مثال: استخدام $fetch لمعالجة الأحداث
<script setup lang="ts">
// $fetch للطلبات أحادية (لا حاجة لـ SSR payload)
async function submitOrder() {
const order = await $fetch('/api/orders', {
method: 'POST',
body: { items: cart.value, total: totalPrice.value }
})
navigateTo(`/orders/${order.id}`)
}
</script>
الناتج:
// Execution Successful
(2) ▶ مثال: استخدام $fetch في API الخادم
// server/api/products/index.get.ts
export default defineEventHandler(async (event) => {
// $fetch هو الخيار الصحيح داخل معالجات الخادم
const query = getQuery(event)
const products = await $fetch('https://api.supplier.com/products', {
params: { category: query.category }
})
return products
})
الناتج:
// Execution Successful
5. شرح مفصل لخيارات الطلب
(1) مرجع سريع للخيارات الرئيسية
| الخيار | النوع | الافتراضي | الوصف |
|---|---|---|---|
| server | boolean | true | هل يُنفّذ على الخادم |
| lazy | boolean | false | هل يُفعّل التحميل الكسول (تنقل غير حظر) |
| immediate | boolean | true | هل يُنفّذ فورًا |
| dedupe | string | 'cancel' | سياسة الطلب المكرر |
| transform | function | - | تحويل بيانات الاستجابة |
| pick | array | - | استخراج الحقول المحددة فقط |
| default | function | - | القيمة الافتراضية قبل تحميل البيانات |
(1) ▶ مثال: وضع Lazy (تنقل غير حظر)
<template>
<div>
<h1>Product Recommendations</h1>
<!-- lazy: البيانات تُحمل في الخلفية، الصفحة تُصيّر فورًا -->
<div v-if="pending">Loading recommendations...</div>
<div v-else>
<ProductCard v-for="p in recommendations" :key="p.id" :product="p" />
</div>
</div>
</template>
<script setup lang="ts">
const { data: recommendations, pending } = await useFetch('/api/recommendations', {
lazy: true
})
</script>
الناتج:
// Execution Successful
(2) ▶ مثال: transform و pick
<script setup lang="ts">
// Transform: معالجة البيانات قبل التخزين
const { data: products } = await useFetch('/api/products', {
transform: (data: any[]) => {
return data.map(p => ({
...p,
formattedPrice: new Intl.NumberFormat('en-US', {
style: 'currency', currency: 'USD'
}).format(p.price)
}))
}
})
// Pick: استخراج حقول محددة فقط (تقليل حجم payload)
const { data: productNames } = await useFetch('/api/products', {
pick: ['id', 'name', 'price']
})
</script>
الناتج:
// Execution Successful
(3) ▶ مثال: server: false (جلب من جانب العميل فقط)
<script setup lang="ts">
// تخطي جلب SSR - جلب من جانب العميل فقط
const { data: userWishlist } = await useFetch('/api/wishlist', {
server: false,
default: () => [] // القيمة الافتراضية قبل تحميل البيانات
})
</script>
الناتج:
// Execution Successful
6. التحديث والاستقصاء
(1) مقارنة استراتيجيات التحديث
| الاستراتيجية | النهج | السيناريو | التكرار |
|---|---|---|---|
| تحديث يدوي | refresh() | بعد إجراء المستخدم | عند الطلب |
| تحديث تلقائي بالمراقبة | خيارات watch | عند تغير المعلمات | عند تغير المعلمات |
| استقصاء | setInterval + refresh | بيانات فورية | مجدول |
| فوري | WebSocket | فائق الفورية | دفع |
(1) ▶ مثال: التحديث اليدوي
<template>
<div>
<h1>Products</h1>
<button @click="refresh()" :disabled="pending">
{{ pending ? 'Refreshing...' : 'Refresh' }}
</button>
<ProductCard v-for="p in products" :key="p.id" :product="p" />
</div>
</template>
<script setup lang="ts">
const { data: products, pending, refresh } = await useFetch('/api/products')
</script>
الناتج:
// Execution Successful
(2) ▶ مثال: تحديث تلقائي عند تغير قيمة المعلمات
<template>
<div>
<select v-model="selectedCategory">
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<ProductCard v-for="p in products" :key="p.id" :product="p" />
</div>
</template>
<script setup lang="ts">
const selectedCategory = ref('all')
// إعادة جلب تلقائية عند تغير selectedCategory
const { data: products } = await useFetch('/api/products', {
query: { category: selectedCategory },
watch: [selectedCategory]
})
</script>
الناتج:
// Execution Successful
(3) ▶ مثال: استقصاء المخزون الفوري
<script setup lang="ts">
const { data: stock, refresh } = await useFetch('/api/stock/live', {
server: false // استقصاء من جانب العميل فقط
})
// استقصاء كل 30 ثانية
const pollInterval = setInterval(() => {
refresh()
}, 30000)
onUnmounted(() => clearInterval(pollInterval))
</script>
الناتج:
// Execution Successful
7. مثال شامل: الجلب المسبق لـ SSR لقائمة منتجات MegaShop
<!-- pages/products/index.vue -->
<template>
<div class="product-list-page">
<h1>MegaShop Products</h1>
<!-- الفلاتر -->
<div class="filters">
<select v-model="filters.category" @change="applyFilters">
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
<option value="home">Home & Garden</option>
</select>
<select v-model="filters.sort">
<option value="popular">Most Popular</option>
<option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option>
</select>
</div>
<!-- حالة التحميل -->
<div v-if="pending" class="loading">Loading products...</div>
<!-- حالة الخطأ -->
<div v-else-if="error" class="error">
Failed to load products. <button @click="refresh()">Retry</button>
</div>
<!-- شبكة المنتجات -->
<div v-else class="product-grid">
<ProductCard
v-for="p in products"
:key="p.id"
:product="p"
@add-to-cart="addToCart"
/>
</div>
<!-- التصفح -->
<div class="pagination">
<button :disabled="page <= 1" @click="page--">Previous</button>
<span>Page {{ page }}</span>
<button @click="page++">Next</button>
</div>
</div>
</template>
<script setup lang="ts">
interface Product {
id: number; name: string; price: number; image: string
}
const page = ref(1)
const filters = reactive({
category: '',
sort: 'popular'
})
// جلب مسبق لـ SSR مع استعلام تفاعلي
const { data: products, pending, error, refresh } = await useFetch<Product[]>('/api/products', {
query: computed(() => ({
page: page.value,
category: filters.category || undefined,
sort: filters.sort
})),
default: () => [],
transform: (data: Product[]) => data.map(p => ({
...p,
formattedPrice: `$${p.price.toLocaleString()} USD`
}))
})
const cart = useState<Product[]>('cart', () => [])
function addToCart(product: Product) {
cart.value.push(product)
}
function applyFilters() {
page.value = 1
refresh()
}
// مراقبة تغير الصفحة للتحديث التلقائي
watch(page, () => refresh())
</script>
❓ أسئلة شائعة
📖ملخص
- useFetch = جلب مسبق لـ SSR + إماهة من جانب العميل + حالة تفاعلية؛ هو الطريقة المفضلة لجلب بيانات الصفحة
- $fetch هو أداة طلب خام، مناسبة لمعالجة الأحداث واستدعاءات API الخادم الداخلية
- "lazy" للتنقل غير الحاصر، "server: false" لتخطي SSR، و "transform/pick" لتحسين البيانات
- refresh() للتحديث اليدوي؛ خيار watch يتتبع تلقائيًا تغيرات المعلمات
- MegaShop يستخدم useFetch و watch لتنفيذ الجلب المسبق لـ SSR لقوائم المنتجات ومزامنة الفلاتر
📝تمارين
- تمرين أساسي (الصعوبة: ⭐): استخدم useFetch لاسترجاع البيانات من API عام (مثل jsonplaceholder)، اعرض قائمة البيانات، وتحقق من عمل SSR بفحص المصدر لرؤية ما إذا كانت البيانات مضمّنة.
- تمرين متقدم (الصعوبة: ⭐⭐): نفّذ قائمة منتجات مع تصفح تُعيد تحميل البيانات تلقائيًا باستخدام watch عند النقر على زر "الصفحة التالية".
- تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ الجلب المسبق لـ SSR للمنتجات الأساسية والتحميل الكسول للمنتجات الموصى بها في وقت واحد، وقارن الفروقات في تجربة المستخدم بين الطريقتين.
---|



