404 Not Found

404 Not Found


nginx

استرجاع البيانات: useFetch

عندما فتحت Alice صفحة MegaShop الرئيسية، رأت صفحة فارغة—لم تُحضر بيانات المنتجات حتى أجرى العميل طلبًا، واستغرق التصيير 3 ثوانٍ. عرف Charlie أن useFetch في Nuxt 3 يمكنه تحميل البيانات مسبقًا على جانب الخادم، مما يسمح لـ HTML بتضمين معلومات المنتج مباشرة ليكون المحتوى مرئيًا فورًا على الشاشة الأولى.

1. ما ستتعلمه


2. قصة حقيقية من مستهلكة

(1) نقطة الألم: شاشة فارغة لمدة 3 ثوانٍ

فتحت Alice موقع MegaShop على هاتفها ورأت شاشة فارغة مع أيقودة دوران لمدة 3 ثوانٍ قبل ظهور المنتجات. كان الأمر أسوأ في المتصفح على المترو باتصال شبكة ضعيف—شاشة فارغة لمدة 5 ثوانٍ. حلل Charlie الموقف ووجد أن بيانات المنتجات لا تُحضر حتى ينتهي تحميل JavaScript من جانب العميل. عملية التصيير بالكامل تتبع هذا التسلسل: تحميل JavaScript ← التنفيذ ← الجلب ← التصيير—وهو أمر بطيء للغاية.

(2) حل الجلب المسبق لـ SSR باستخدام useFetch

يقوم useFetch بجلب البيانات أثناء مرحلة SSR، ويضمّن HTML محتوى المنتج مباشرة:

VUE
<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 حتى إماهة العميل

100%
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

VUE
<!-- 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>

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: معالج مخصص لـ useAsyncData

VUE
<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>

الناتج:

TEXT
// Execution Successful

4. $fetch مقابل useFetch

(1) مقارنة الفروقات الرئيسية

البُعد $fetch useFetch
تمرير بيانات SSR ❌ بدون تمرير ✅ تمرير payload تلقائي
تفاعلية ❌ بيانات خام ✅ ref تفاعلية
إدارة الحالة ❌ لا يوجد ✅ pending/error/refresh
طلب مكرر ❌可能 تكرار ✅ إلغاء التكرار
حالات الاستخدام معالجة الأحداث/مسار API استرجاع بيانات الصفحة

(1) ▶ مثال: استخدام $fetch لمعالجة الأحداث

VUE
<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>

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: استخدام $fetch في API الخادم

TYPESCRIPT
// 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
})

الناتج:

TEXT
// Execution Successful

5. شرح مفصل لخيارات الطلب

(1) مرجع سريع للخيارات الرئيسية

الخيار النوع الافتراضي الوصف
server boolean true هل يُنفّذ على الخادم
lazy boolean false هل يُفعّل التحميل الكسول (تنقل غير حظر)
immediate boolean true هل يُنفّذ فورًا
dedupe string 'cancel' سياسة الطلب المكرر
transform function - تحويل بيانات الاستجابة
pick array - استخراج الحقول المحددة فقط
default function - القيمة الافتراضية قبل تحميل البيانات

(1) ▶ مثال: وضع Lazy (تنقل غير حظر)

VUE
<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>

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: transform و pick

VUE
<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>

الناتج:

TEXT
// Execution Successful

(3) ▶ مثال: server: false (جلب من جانب العميل فقط)

VUE
<script setup lang="ts">
// تخطي جلب SSR - جلب من جانب العميل فقط
const { data: userWishlist } = await useFetch('/api/wishlist', {
  server: false,
  default: () => [] // القيمة الافتراضية قبل تحميل البيانات
})
</script>

الناتج:

TEXT
// Execution Successful

6. التحديث والاستقصاء

(1) مقارنة استراتيجيات التحديث

الاستراتيجية النهج السيناريو التكرار
تحديث يدوي refresh() بعد إجراء المستخدم عند الطلب
تحديث تلقائي بالمراقبة خيارات watch عند تغير المعلمات عند تغير المعلمات
استقصاء setInterval + refresh بيانات فورية مجدول
فوري WebSocket فائق الفورية دفع

(1) ▶ مثال: التحديث اليدوي

VUE
<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>

الناتج:

TEXT
// Execution Successful

(2) ▶ مثال: تحديث تلقائي عند تغير قيمة المعلمات

VUE
<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>

الناتج:

TEXT
// Execution Successful

(3) ▶ مثال: استقصاء المخزون الفوري

VUE
<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>

الناتج:

TEXT
// Execution Successful

7. مثال شامل: الجلب المسبق لـ SSR لقائمة منتجات MegaShop

VUE
<!-- 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 في قيام العميل بطلبات مكررة؟
ج لا. آلية payload في Nuxt 3 تسلسل البيانات المُحضّرة أثناء SSR في HTML، والتي يستخدمها العميل مباشرة أثناء الإماهة، لذلك لا تُجرى طلبات مكررة.
س متى يجب استخدام $fetch بدلاً من useFetch؟
ج استخدم $fetch في معالجات الأحداث (مثل النقر على زر لإرسال نموذج) أو عند استدعائه من داخل API الخادم. استخدم useFetch لجلب البيانات الأولية للصفحة.
س كيف يُولّد مفتاح useFetch؟
ج افتراضيًا، يُولّد مفتاح فريد تلقائيًا باستخدام URL وخيارات الطلب. إذا كنت بحاجة للتحكم يدويًا (مثل مشاركة التخزين المؤقت عبر طلبات متعددة)، يمكنك تمرير معامل key.
س ما الفرق بين lazy و server: false؟
ج lazy لا يزال ينفذ الطلب أثناء SSR، لكنه لا يحظر التنقل (الصفحة تُصيّر أولًا ثم تنتظر البيانات). server: false يتخطى طلب SSR بالكامل ويجلب البيانات من جانب العميل فقط.
س لماذا بيانات استجابة useFetch هي ref وليست قيمة خامة؟
ج لأن البيانات قد تتغير (refresh/watch)، و ref يضمن التفاعلية. عند التفكيك، استخدم toRefs أو الوصول مباشرة عبر .value.
س ما الفرق بين خياري "cancel" و "defer" لـ dedupe؟
ج "cancel" (الافتراضي) يلغي الطلب السابق غير المكتمل ويبدأ واحدًا جديدًا. "defer" يعيد استخدام نتيجة الطلب السابق ولا يبدأ واحدًا جديدًا.

📖ملخص


📝تمارين

  1. تمرين أساسي (الصعوبة: ⭐): استخدم useFetch لاسترجاع البيانات من API عام (مثل jsonplaceholder)، اعرض قائمة البيانات، وتحقق من عمل SSR بفحص المصدر لرؤية ما إذا كانت البيانات مضمّنة.
  2. تمرين متقدم (الصعوبة: ⭐⭐): نفّذ قائمة منتجات مع تصفح تُعيد تحميل البيانات تلقائيًا باستخدام watch عند النقر على زر "الصفحة التالية".
  3. تحدٍ (الصعوبة: ⭐⭐⭐): نفّذ الجلب المسبق لـ SSR للمنتجات الأساسية والتحميل الكسول للمنتجات الموصى بها في وقت واحد، وقارن الفروقات في تجربة المستخدم بين الطريقتين.

---|

Web-Tutorial.com

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

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

100%