أنماط جلب البيانات

هناك بعض الأنماط والممارسات الموصى بها لجلب البيانات في React وNext.js. ستغطي هذه الصفحة بعض الأنماط الأكثر شيوعًا وكيفية استخدامها.

جلب البيانات على الخادم

عندما يكون ذلك ممكنًا، نوصي بجلب البيانات على الخادم. هذا يسمح لك بـ:

  • الوصول المباشر إلى موارد البيانات الخلفية (مثل قواعد البيانات).
  • الحفاظ على تطبيقك أكثر أمانًا عن طريق منع وصول المعلومات الحساسة مثل رموز الوصول ومفاتيح API إلى العميل.
  • جلب البيانات وعرضها في نفس البيئة. هذا يقلل من التواصل ذهابًا وإيابًا بين العميل والخادم، وكذلك العمل على الخيط الرئيسي على العميل.
  • تنفيذ عمليات جلب بيانات متعددة برحلة ذهاب وإياب واحدة بدلاً من طلبات فردية متعددة على العميل.
  • تقليل شلالات العميل-الخادم.
  • اعتمادًا على منطقتك، يمكن أن يحدث جلب البيانات بالقرب من مصدر البيانات، مما يقلل من زمن الوصول ويحسن الأداء.

يمكنك جلب البيانات على الخادم باستخدام مكونات الخادم، معالجات المسارات، وأفعال الخادم.

جلب البيانات حيثما تكون مطلوبة

إذا كنت بحاجة إلى استخدام نفس البيانات (مثل المستخدم الحالي) في عدة مكونات في شجرة، ليس عليك جلب البيانات عالميًا، ولا تمرير الخصائص بين المكونات. بدلاً من ذلك، يمكنك استخدام fetch أو cache من React في المكون الذي يحتاج إلى البيانات دون القلق بشأن تأثيرات الأداء لإنشاء طلبات متعددة لنفس البيانات.

هذا ممكن لأن طلبات fetch يتم تخزينها مؤقتًا تلقائيًا. تعلم المزيد عن تخزين الطلبات مؤقتًا

جيد أن تعرف: هذا ينطبق أيضًا على التخطيطات، لأنه لا يمكن تمرير البيانات بين التخطيط الأب وأطفاله.

البث (Streaming)

البث وSuspense هما ميزتان في React تسمحان لك بعرض وحدات واجهة المستخدم تدريجيًا وبثها بشكل تدريجي إلى العميل.

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

عرض الخادم مع البث

للمزيد عن البث وSuspense، راجع صفحات واجهة التحميل والبث مع Suspense.

جلب البيانات المتوازي والتسلسلي

عند جلب البيانات داخل مكونات React، تحتاج إلى الانتباه لنمطين لجلب البيانات: المتوازي والتسلسلي.

جلب البيانات التسلسلي والمتوازي
  • مع جلب البيانات التسلسلي، تكون الطلبات في المسار معتمدة على بعضها البعض وبالتالي تخلق شلالات. قد تكون هناك حالات تريد فيها هذا النمط لأن أحد الطلبات يعتمد على نتيجة الآخر، أو تريد أن يتم استيفاء شرط قبل الطلب التالي لتوفير الموارد. ومع ذلك، قد يكون هذا السلوك غير مقصود ويؤدي إلى أوقات تحميل أطول.
  • مع جلب البيانات المتوازي، يتم بدء الطلبات في المسار بسرعة وسيتم تحميل البيانات في نفس الوقت. هذا يقلل من شلالات العميل-الخادم والوقت الإجمالي لتحميل البيانات.

جلب البيانات التسلسلي

إذا كان لديك مكونات متداخلة، وكل مكون يجلب بياناته الخاصة، فإن جلب البيانات سيحدث بشكل تسلسلي إذا كانت هذه الطلبات مختلفة (هذا لا ينطبق على الطلبات لنفس البيانات حيث يتم تخزينها مؤقتًا تلقائيًا memoized).

على سبيل المثال، لن يبدأ مكون Playlists في جلب البيانات إلا بعد أن ينتهي مكون Artist من جلب البيانات لأن Playlists يعتمد على خاصية artistID:

// ...

async function Playlists({ artistID }: { artistID: string }) {
  // انتظر قوائم التشغيل
  const playlists = await getArtistPlaylists(artistID)

  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  )
}

export default async function Page({
  params: { username },
}: {
  params: { username: string }
}) {
  // انتظر الفنان
  const artist = await getArtist(username)

  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}
// ...

async function Playlists({ artistID }) {
  // انتظر قوائم التشغيل
  const playlists = await getArtistPlaylists(artistID)

  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  )
}

export default async function Page({ params: { username } }) {
  // انتظر الفنان
  const artist = await getArtist(username)

  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}

في مثل هذه الحالات، يمكنك استخدام loading.js (لقطاعات المسار) أو React <Suspense> (للمكونات المتداخلة) لعرض حالة تحميل فورية بينما يقوم React ببث النتيجة.

هذا سيمنع حظر المسار بالكامل بسبب جلب البيانات، وسيتمكن المستخدم من التفاعل مع أجزاء الصفحة غير المحظورة.

طلبات البيانات الحاجزة:

نهج بديل لمنع الشلالات هو جلب البيانات عالميًا، في جذر تطبيقك، ولكن هذا سيحجب عرض جميع قطاعات المسار تحته حتى تنتهي البيانات من التحميل. يمكن وصف هذا بـ "الكل أو لا شيء" في جلب البيانات. إما أن يكون لديك جميع البيانات لصفحتك أو تطبيقك، أو لا شيء.

أي طلبات fetch مع await ستحجب العرض وجلب البيانات للشجرة بأكملها تحتها، ما لم يتم تغليفها في حدود <Suspense> أو استخدام loading.js. نهج آخر هو استخدام جلب البيانات المتوازي أو نمط التحميل المسبق.

جلب البيانات المتوازي

لجلب البيانات بشكل متوازي، يمكنك بدء الطلبات بسرعة عن طريق تعريفها خارج المكونات التي تستخدم البيانات، ثم استدعائها من داخل المكون. هذا يوفر الوقت عن طريق بدء كلا الطلبين في نفس الوقت، ولكن لن يرى المستخدم النتيجة المعروضة حتى يتم حل كلا الوعدين.

في المثال أدناه، يتم تعريف الدالتين getArtist وgetArtistAlbums خارج مكون Page، ثم يتم استدعاؤهما داخل المكون، وننتظر حل كلا الوعدين:

import Albums from './albums'

async function getArtist(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}`)
  return res.json()
}

async function getArtistAlbums(username: string) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`)
  return res.json()
}

export default async function Page({
  params: { username },
}: {
  params: { username: string }
}) {
  // ابدأ كلا الطلبين في نفس الوقت
  const artistData = getArtist(username)
  const albumsData = getArtistAlbums(username)

  // انتظر حل الوعدين
  const [artist, albums] = await Promise.all([artistData, albumsData])

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  )
}
import Albums from './albums'

async function getArtist(username) {
  const res = await fetch(`https://api.example.com/artist/${username}`)
  return res.json()
}

async function getArtistAlbums(username) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`)
  return res.json()
}

export default async function Page({ params: { username } }) {
  // ابدأ كلا الطلبين في نفس الوقت
  const artistData = getArtist(username)
  const albumsData = getArtistAlbums(username)

  // انتظر حل الوعدين
  const [artist, albums] = await Promise.all([artistData, albumsData])

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  )
}

لتحسين تجربة المستخدم، يمكنك إضافة حدود Suspense لتقسيم عمل العرض وإظهار جزء من النتيجة في أسرع وقت ممكن.

التحميل المسبق للبيانات

طريقة أخرى لمنع الشلالات هي استخدام نمط التحميل المسبق. يمكنك إنشاء دالة preload اختيارية لتحسين جلب البيانات المتوازي. مع هذا النهج، لا تحتاج إلى تمرير الوعود كخصائص. يمكن أن يكون لدالة preload أي اسم لأنها نمط وليس واجهة برمجة تطبيقات.

import { getItem } from '@/utils/get-item'

export const preload = (id: string) => {
  // void تقيم التعبير المعطى وتعيد undefined
  // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
  void getItem(id)
}
export default async function Item({ id }: { id: string }) {
  const result = await getItem(id)
  // ...
}
import { getItem } from '@/utils/get-item'

export const preload = (id) => {
  // void تقيم التعبير المعطى وتعيد undefined
  // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
  void getItem(id)
}
export default async function Item({ id }) {
  const result = await getItem(id)
  // ...
}
import Item, { preload, checkIsAvailable } from '@/components/Item'

export default async function Page({
  params: { id },
}: {
  params: { id: string }
}) {
  // بدء تحميل بيانات العنصر
  preload(id)
  // تنفيذ مهمة غير متزامنة أخرى
  const isAvailable = await checkIsAvailable()

  return isAvailable ? <Item id={id} /> : null
}
import Item, { preload, checkIsAvailable } from '@/components/Item'

export default async function Page({ params: { id } }) {
  // بدء تحميل بيانات العنصر
  preload(id)
  // تنفيذ مهمة غير متزامنة أخرى
  const isAvailable = await checkIsAvailable()

  return isAvailable ? <Item id={id} /> : null
}

استخدام React cache، server-only، ونمط التحميل المسبق

يمكنك الجمع بين دالة cache، ونمط preload، وحزمة server-only لإنشاء أداة جلب بيانات يمكن استخدامها في جميع أنحاء تطبيقك.

import { cache } from 'react'
import 'server-only'

export const preload = (id: string) => {
  void getItem(id)
}

export const getItem = cache(async (id: string) => {
  // ...
})
import { cache } from 'react'
import 'server-only'

export const preload = (id) => {
  void getItem(id)
}

export const getItem = cache(async (id) => {
  // ...
})

مع هذا النهج، يمكنك جلب البيانات بسرعة، تخزين الردود مؤقتًا، وضمان أن جلب البيانات هذا يحدث فقط على الخادم.

يمكن استخدام صادرات utils/get-item بواسطة التخطيطات، الصفحات، أو المكونات الأخرى لمنحها التحكم في وقت جلب بيانات العنصر.

جيد أن تعرف:

  • نوصي باستخدام حزمة server-only للتأكد من عدم استخدام دوال جلب بيانات الخادم على العميل أبدًا.