كيفية جلب البيانات والتدفق
سترشدك هذه الصفحة حول كيفية جلب البيانات في مكونات الخادم والعميل (Server and Client Components)، وكيفية تدفق المكونات التي تعتمد على البيانات.
جلب البيانات
مكونات الخادم
يمكنك جلب البيانات في مكونات الخادم باستخدام:
مع fetch
API
لجلب البيانات باستخدام fetch
API، حول مكونك إلى دالة غير متزامنة (async)، واستخدم await مع استدعاء fetch
. على سبيل المثال:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
جيد أن تعرف:
- استجابات
fetch
لا يتم تخزينها مؤقتًا افتراضيًا. ومع ذلك، سيقوم Next.js بالتقديم المسبق (prerender) للمسار وسيتم تخزين الناتج لتحسين الأداء. إذا كنت ترغب في تفعيل التقديم الديناميكي (dynamic rendering)، استخدم الخيار{ cache: 'no-store' }
. راجع مرجعfetch
API.- أثناء التطوير، يمكنك تسجيل استدعاءات
fetch
لتحسين الرؤية وتصحيح الأخطاء. راجع مرجعlogging
API.
مع ORM أو قاعدة بيانات
بما أن مكونات الخادم يتم تقديمها على الخادم، يمكنك بأمان إجراء استعلامات قاعدة بيانات باستخدام ORM أو عميل قاعدة بيانات. حول مكونك إلى دالة غير متزامنة واستخدم await مع الاستدعاء:
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
مكونات العميل
هناك طريقتان لجلب البيانات في مكونات العميل، باستخدام:
- خطاف
use
في React - مكتبة مجتمعية مثل SWR أو React Query
تدفق البيانات مع خطاف use
يمكنك استخدام خطاف use
في React لتدفق البيانات من الخادم إلى العميل. ابدأ بجلب البيانات في مكون الخادم الخاص بك، ومرر الوعد (promise) إلى مكون العميل كخاصية (prop):
import Posts from '@/app/ui/posts
import { Suspense } from 'react'
export default function Page() {
// لا تستخدم await مع دالة جلب البيانات
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
import Posts from '@/app/ui/posts
import { Suspense } from 'react'
export default function Page() {
// لا تستخدم await مع دالة جلب البيانات
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
ثم، في مكون العميل الخاص بك، استخدم خطاف use
لقراءة الوعد:
'use client'
import { use } from 'react'
export default function Posts({
posts,
}: {
posts: Promise<{ id: string; title: string }[]>
}) {
const allPosts = use(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const posts = use(posts)
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
في المثال أعلاه، تم تغليف مكون <Posts>
داخل حدود <Suspense>
. هذا يعني أنه سيتم عرض الحالة الاحتياطية (fallback) أثناء حل الوعد. تعلم المزيد عن التدفق.
المكتبات المجتمعية
يمكنك استخدام مكتبة مجتمعية مثل SWR أو React Query لجلب البيانات في مكونات العميل. هذه المكتبات لها دلالاتها الخاصة للتخزين المؤقت والتدفق والميزات الأخرى. على سبيل المثال، مع SWR:
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
إزالة تكرار الطلبات مع React.cache
إزالة التكرار هي عملية منع الطلبات المكررة لنفس المورد أثناء تمرير التقديم. تتيح لك جلب نفس البيانات في مكونات مختلفة مع منع طلبات متعددة إلى مصدر البيانات الخاص بك.
إذا كنت تستخدم fetch
، يمكن إزالة تكرار الطلبات بإضافة cache: 'force-cache'
. هذا يعني أنه يمكنك استدعاء نفس الرابط بنفس الخيارات بأمان، وسيتم إجراء طلب واحد فقط.
إذا كنت لا تستخدم fetch
، وتستخدم بدلاً من ذلك ORM أو قاعدة بيانات مباشرة، يمكنك تغليف جلب البيانات الخاصة بك باستخدام دالة React cache
.
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
export const getPost = cache(async (id: string) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
import { cache } from 'react'
import { db, posts, eq } from '@/lib/db'
import { notFound } from 'next/navigation'
export const getPost = cache(async (id) => {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(id)),
})
})
التدفق
تحذير: المحتوى أدناه يفترض تمكين خيار التكوين
dynamicIO
في تطبيقك. تم تقديم هذه العلامة في Next.js 15 canary.
عند استخدام async/await
في مكونات الخادم، سيقوم Next.js بالانتقال إلى التقديم الديناميكي (dynamic rendering). هذا يعني أن البيانات سيتم جلبها وتقديمها على الخادم لكل طلب مستخدم. إذا كانت هناك أي طلبات بيانات بطيئة، فسيتم حظر المسار بالكامل من التقديم.
لتحسين وقت التحميل الأولي وتجربة المستخدم، يمكنك استخدام التدفق لتقسيم HTML للصفحة إلى أجزاء أصغر وإرسال هذه الأجزاء تدريجياً من الخادم إلى العميل.

هناك طريقتان يمكنك من خلالهما تنفيذ التدفق في تطبيقك:
- تغليف صفحة بملف
loading.js
- تغليف مكون ب
<Suspense>
مع loading.js
يمكنك إنشاء ملف loading.js
في نفس مجلد صفحتك لتدفق الصفحة بأكملها أثناء جلب البيانات. على سبيل المثال، لتدفق app/blog/page.js
، أضف الملف داخل مجلد app/blog
.

export default function Loading() {
// حدد واجهة التحميل هنا
return <div>Loading...</div>
}
export default function Loading() {
// حدد واجهة التحميل هنا
return <div>Loading...</div>
}
عند التنقل، سيرى المستخدم على الفور التخطيط وحالة التحميل أثناء تقديم الصفحة. سيتم بعد ذلك تبديل المحتوى الجديد تلقائيًا بمجرد اكتمال التقديم.

خلف الكواليس، سيتم تداخل loading.js
داخل layout.js
، وسيتم تغليف page.js
وأي أطفال أدناه تلقائيًا داخل حدود <Suspense>
.

يعمل هذا النهج جيدًا لمقاطع المسار (التخطيطات والصفحات)، ولكن لمزيد من التدفق الدقيق، يمكنك استخدام <Suspense>
.
مع <Suspense>
يسمح لك <Suspense>
بأن تكون أكثر دقة بشأن الأجزاء التي تريد تدفقها من الصفحة. على سبيل المثال، يمكنك عرض أي محتوى صفحة يقع خارج حدود <Suspense>
على الفور، وتدفق قائمة مشاركات المدونة داخل الحدود.
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* سيتم إرسال هذا المحتوى إلى العميل على الفور */}
<header>
<h1>Welcome to the Blog</h1>
<p>Read the latest posts below.</p>
</header>
<main>
{/* أي محتوى مغلف داخل حدود <Suspense> سيتم تدفقه */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* سيتم إرسال هذا المحتوى إلى العميل على الفور */}
<header>
<h1>Welcome to the Blog</h1>
<p>Read the latest posts below.</p>
</header>
<main>
{/* أي محتوى مغلف داخل حدود <Suspense> سيتم تدفقه */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
إنشاء حالات تحميل ذات معنى
حالة التحميل الفورية هي واجهة مستخدم احتياطية يتم عرضها للمستخدم فورًا بعد التنقل. للحصول على أفضل تجربة مستخدم، نوصي بتصميم حالات تحميل ذات معنى وتساعد المستخدمين على فهم أن التطبيق يستجيب. على سبيل المثال، يمكنك استخدام هياكل عظمية (skeletons) ودوائر تحميل، أو جزء صغير ولكن ذو معنى من الشاشات المستقبلية مثل صورة الغلاف والعنوان وما إلى ذلك.
في التطوير، يمكنك معاينة وفحص حالة التحميل لمكوناتك باستخدام React Devtools.
أمثلة
جلب البيانات التسلسلي
يحدث جلب البيانات التسلسلي عندما تقوم المكونات المتداخلة في شجرة كل منها بجلب بياناتها الخاصة ولا يتم إزالة تكرار الطلبات، مما يؤدي إلى أوقات استجابة أطول.

قد تكون هناك حالات تريد فيها هذا النمط لأن أحد عمليات الجلب يعتمد على نتيجة الآخر.
على سبيل المثال، سيبدأ مكون <Playlists>
بجلب البيانات فقط بعد انتهاء مكون <Artist>
من جلب البيانات لأن <Playlists>
يعتمد على خاصية artistID
:
export default async function Page({
params,
}: {
params: Promise<{ username: string }>
}) {
const { username } = await params
// الحصول على معلومات الفنان
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
{/* عرض واجهة احتياطية أثناء تحميل مكون Playlists */}
<Suspense fallback={<div>Loading...</div>}>
{/* تمرير معرف الفنان إلى مكون Playlists */}
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
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 }) {
const { username } = await params
// الحصول على معلومات الفنان
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
{/* عرض واجهة احتياطية أثناء تحميل مكون Playlists */}
<Suspense fallback={<div>Loading...</div>}>
{/* تمرير معرف الفنان إلى مكون Playlists */}
<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>
)
}
لتحسين تجربة المستخدم، يجب عليك استخدام React <Suspense>
لعرض fallback
أثناء جلب البيانات. سيؤدي هذا إلى تمكين التدفق ومنع حظر المسار بالكامل بواسطة طلبات البيانات التسلسلية.
جلب البيانات المتوازي (Parallel data fetching)
يحدث جلب البيانات المتوازي عندما يتم بدء طلبات البيانات في المسار (route) بشكل متحمس (eagerly) وتنفيذها في نفس الوقت.
بشكل افتراضي، يتم عرض التخطيطات والصفحات بالتوازي. لذا يبدأ كل مقطع (segment) بجلب البيانات في أسرع وقت ممكن.
ومع ذلك، داخل أي مكون (component)، يمكن أن تظل طلبات async
/await
المتعددة متتابعة (sequential) إذا تم وضعها بعد بعضها البعض. على سبيل المثال، سيتم حظر getAlbums
حتى يتم حل getArtist
:
import { getArtist, getAlbums } from '@/app/lib/data'
export default async function Page({ params }) {
// These requests will be sequential
const { username } = await params
const artist = await getArtist(username)
const albums = await getAlbums(username)
return <div>{artist.name}</div>
}
يمكنك بدء الطلبات بالتوازي عن طريق تعريفها خارج المكونات التي تستخدم البيانات، وحلها معًا، على سبيل المثال باستخدام Promise.all
:
import Albums from './albums'
async function getArtist(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getAlbums(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({
params,
}: {
params: Promise<{ username: string }>
}) {
const { username } = await params
const artistData = getArtist(username)
const albumsData = getAlbums(username)
// Initiate both requests in parallel
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums} />
</>
)
}
import Albums from './albums'
async function getArtist(username) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getAlbums(username) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({ params }) {
const { username } = await params
const artistData = getArtist(username)
const albumsData = getAlbums(username)
// Initiate both requests in parallel
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums} />
</>
)
}
معلومة مفيدة: إذا فشل أحد الطلبات عند استخدام
Promise.all
، فستفشل العملية بأكملها. للتعامل مع هذا، يمكنك استخدام طريقةPromise.allSettled
بدلاً من ذلك.
جلب البيانات المسبق (Preloading data)
يمكنك جلب البيانات مسبقًا عن طريق إنشاء دالة مساعدة (utility function) تقوم باستدعائها بشكل متحمس قبل الطلبات الحاجزة (blocking requests). يقوم <Item>
بعرض مشروط بناءً على دالة checkIsAvailable()
.
يمكنك استدعاء preload()
قبل checkIsAvailable()
لبدء تبعيات بيانات <Item/>
بشكل متحمس. بحلول وقت عرض <Item/>
، تكون بياناته قد تم جلبها بالفعل.
import { getItem } from '@/lib/data'
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
// starting loading item data
preload(id)
// perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
export const preload = (id: string) => {
// void evaluates the given expression and returns undefined
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
import { getItem } from '@/lib/data'
export default async function Page({ params }) {
const { id } = await params
// starting loading item data
preload(id)
// perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
export const preload = (id) => {
// void evaluates the given expression and returns undefined
// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export async function Item({ id }) {
const result = await getItem(id)
// ...
بالإضافة إلى ذلك، يمكنك استخدام دالة cache
من React وحزمة server-only
لإنشاء دالة مساعدة قابلة لإعادة الاستخدام. تتيح لك هذه الطريقة تخزين دالة جلب البيانات مؤقتًا (cache) وضمان تنفيذها فقط على الخادم.
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
export const preload = (id) => {
void getItem(id)
}
export const getItem = cache(async (id) => {
// ...
})