Next.js
مسیریاب app
برنامه تان را بسیازید
دریافت داده
الگوهای دریافت داده و بهترین روش ها

الگوهای دریافت داده و بهترین روش ها

چند الگوی توصیه شده و بهترین روش برای دریافت داده در React و Next.js وجود دارد. در این صفحه به برخی از رایج ترین الگوها و نحوه استفاده از آنها می پردازیم.

دریافت داده در سرور

هر زمان که ممکن است، توصیه می‌کنیم داده‌ها را با کامپوننت های سرور در سرور دریافت کنید. این به شما امکان می دهد:

  • دسترسی مستقیم به منابع داده بک‌اند (مانند پایگاه‌های داده) داشته باشید.
  • با جلوگیری از افشای اطلاعات حساس مانند توکن‌های دسترسی و کلیدهای API به کلاینت، برنامه خود را امن‌تر نگه دارید.
  • داده ها را در همان محیط دریافت و ارائه کنید. این باعث کاهش هر دو ارتباط بین کلاینت و سرور و همچنین کار روی رشته اصلی (opens in a new tab) در کلاینت می شود.
  • انجام چندین دریافت داده با یک رفت و برگشت به جای درخواست های فردی متعدد در کلاینت.
  • waterfalls client-server را کاهش دهید.
  • بسته به منطقه شما، دریافت داده همچنین می تواند نزدیکتر به منبع داده شما رخ دهد و تأخیر را کاهش دهد و عملکرد را بهبود بخشد.

سپس می توانید داده ها را با اکشن های سرور Server Actions تغییر دهید یا به روز کنید.

دریافت داده در صورت نیاز

اگر نیاز به استفاده از همان داده (به عنوان مثال، کاربر فعلی) در چندین کامپوننت در یک ساختار درختی دارید، نیازی به دریافت داده به صورت جهانی یا انتقال داده ها بین کامپوننت ها از طریق پراپ ها نیست. در عوض، می توانید از fetch یا حافظه cache React در کامپوننتی که به داده نیاز دارد استفاده کنید بدون اینکه نگران تأثیر عملکرد ایجاد چندین درخواست برای همان داده باشید.

این امکان پذیر است زیرا درخواست های fetch به طور خودکار به خاطر سپرده memoized می شوند. در مورد request memoization بیشتر بیاموزید.

خوب است بدانید: این موضوع در مورد طرح بندی ها نیز صدق می کند، زیرا انتقال داده بین یک طرح بندی والد و فرزندان آن امکان پذیر نیست.

استریمینگ

Streaming و Suspense (opens in a new tab) ویژگی های React هستند که به شما امکان می دهند واحدهای رندر شده رابط کاربری را به تدریج رندر کنید و به صورت تدریجی به کلاینت ارسال کنید.

با کامپوننت های سرور و طرح بندی های تودرتو، می توانید بخش هایی از صفحه را که به طور خاص به داده نیاز ندارند را به صورت لحظه‌ای رندر کنید و برای بخش هایی از صفحه که در حال دریافت داده هستند، حالت بارگیری را نشان دهید. این بدان معناست که کاربر مجبور نیست منتظر بارگیری کل صفحه قبل از شروع تعامل با آن باشد.


رندرینگ سرور با استریمینگرندرینگ سرور با استریمینگ

برای اطلاعات بیشتر در مورد استریمینگ و Suspense، صفحات رابط کاربری در حال بارگذاری (Loading UI) و استریمینگ و Suspense را مشاهده کنید.

دریافت داده به صورت موازی و ترتیبی

هنگام دریافت داده در داخل کامپوننت های React، باید از دو الگوی دریافت داده آگاه باشید: موازی و ترتیبی.


دریافت داده به صورت موازی و ترتیبیدریافت داده به صورت موازی و ترتیبی
  • با دریافت داده به صورت ترتیبی، درخواست ها در یک مسیر به هم وابسته هستند و بنابراین آبشار ایجاد می کنند. ممکن است مواردی وجود داشته باشد که بخواهید این الگو را داشته باشید زیرا یک دریافت به نتیجه دیگری بستگی دارد، یا می خواهید یک شرط قبل از دریافت بعدی برای صرفه جویی در منابع برقرار شود. با این حال، این رفتار همچنین می تواند غیرعمدی باشد و منجر به زمان بارگیری طولانی تر شود.
  • با دریافت داده به صورت موازی، درخواست ها در یک مسیر با اشتیاق آغاز می شوند و داده ها را به طور همزمان بارگیری می کنند. این باعث کاهش آبشارهای client-server و کل زمان بارگذاری داده می شود.

دریافت داده به صورت ترتیبی

اگر کامپوننت های تودرتو دارید و هر کامپوننت داده های خاص خود را دریافت می کند، در صورتی که این درخواست های داده متفاوت باشند، دریافت داده به صورت ترتیبی انجام می شود (این برای درخواست های داده های مشابه که به طور خودکار memoized می شوند اعمال نمی شود).

به عنوان مثال، کامپوننت Playlists تنها زمانی شروع به دریافت داده می کند که کامپوننت Artist دریافت داده را به پایان برساند زیرا Playlists به prop artistID بستگی دارد:

app/artist/[username]/page.tsx
// ...
 
async function Playlists({ artistID }: { artistID: string }) {
	// Wait for the playlists
	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 }
}) {
	// Wait for the artist
	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 نتیجه را پخش (streams) می‌کند، استفاده کنید.

این کار از مسدود شدن کل مسیر توسط دریافت داده جلوگیری می کند و کاربر می تواند با قسمت هایی از صفحه که مسدود نشده اند تعامل داشته باشد.

مسدود کردن درخواست های داده:

یک رویکرد جایگزین برای جلوگیری از آبشارها، دریافت داده به صورت جهانی در ریشه برنامه شما است، اما این باعث مسدود شدن رندر برای تمام بخش های مسیر زیر آن تا زمانی که داده ها به طور کامل بارگیری شوند، می شود. این را می توان به عنوان دریافت داده "همه یا هیچ" توصیف کرد. یا داده های کل صفحه یا برنامه خود را دارید یا هیچ داده ای ندارید.

هر درخواست fetch با await رندر و دریافت داده را برای کل درخت زیر آن مسدود می کند، مگر اینکه در یک محدوده <Suspense> قرار داده شود یا از loading.js استفاده شود. راه حل های دیگر استفاده از دریافت داده به صورت موازی یا الگوی پیش بارگیری است.

دریافت داده به صورت موازی

برای دریافت داده به صورت موازی، می توانید با تعریف درخواست ها خارج از کامپوننت هایی که از داده استفاده می کنند، آنها را با اشتیاق آغاز کنید، سپس آنها را از داخل کامپوننت فراخوانی کنید. این کار با شروع هر دو درخواست به طور همزمان باعث صرفه جویی در زمان می شود، با این حال، کاربر نتیجه رندر شده را تا زمانی که هر دو وعده حل نشوند، مشاهده نخواهد کرد.

در مثال زیر، توابع getArtist و getArtistAlbums خارج از کامپوننت Page تعریف شده اند، سپس داخل کامپوننت فراخوانی می شوند و منتظر حل شدن هر دو پرامیس می مانیم:

app/artist/[username]/page.tsx
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 }
}) {
	// Initiate both requests in parallel
	const artistData = getArtist(username)
	const albumsData = getArtistAlbums(username)
 
	// Wait for the promises to resolve
	const [artist, albums] = await Promise.all([artistData, albumsData])
 
	return (
		<>
			<h1>{artist.name}</h1>
			<Albums list={albums}></Albums>
		</>
	)
}

برای بهبود تجربه کاربری، می توانید یک محدوده Suspense Boundary اضافه کنید تا کار رندر را تقسیم بندی کنید و بخشی از نتیجه را هر چه سریعتر به کاربر نمایش دهید.

پیش بارگیری داده

روش دیگری برای جلوگیری از آبشارها استفاده از الگوی پیش بارگذاری (preload) است. به صورت اختیاری می توانید یک تابع preload برای بهینه سازی بیشتر دریافت موازی داده ایجاد کنید. با این رویکرد، نیازی به انتقال وعده ها به عنوان prop ندارید. تابع preload همچنین می تواند هر نامی داشته باشد زیرا یک الگو است، نه یک API.

components/Item.tsx
import { getItem } from '@/utils/get-item'
 
export const preload = (id: string) => {
	// void evaluates the given expression and returns undefined
	// https://developer.mozilla.org/nextjs/Web/JavaScript/Reference/Operators/void
	void getItem(id)
}
export default async function Item({ id }: { id: string }) {
	const result = await getItem(id)
	// ...
}
app/item/[id]/page.tsx
import Item, { preload, checkIsAvailable } from '@/components/Item'
 
export default async function Page({
	params: { id },
}: {
	params: { id: string }
}) {
	// starting loading item data
	preload(id)
	// perform another asynchronous task
	const isAvailable = await checkIsAvailable()
 
	return isAvailable ? <Item id={id} /> : null
}

استفاده از React cache, server-only, و الگوی پیش بارگیری preload

می توانید تابع cache، الگوی preload و پکیج server-only را برای ایجاد یک ابزار مفید دریافت داده ترکیب کنید که در کل برنامه شما قابل استفاده باشد.

utils/get-item.ts
import { cache } from 'react'
import 'server-only'
 
export const preload = (id: string) => {
	void getItem(id)
}
 
export const getItem = cache(async (id: string) => {
	// ...
})

با این رویکرد، می توانید با اشتیاق داده ها را دریافت کنید، پاسخ ها را در حافظه کش ذخیره کنید و تضمین کنید که این دریافت داده فقط در سرور اتفاق می افتد.

خروجی های utils/get-item را می توان توسط طرح بندی ها، صفحات یا سایر کامپوننت ها برای کنترل زمان دریافت داده یک آیتم استفاده کرد.

خوب است بدانید:

  • توصیه می کنیم از پکیج server-only برای اطمینان از اینکه توابع دریافت داده سرور هرگز در کلاینت استفاده نمی شوند، استفاده کنید.

جلوگیری از افشای داده های حساس به کلاینت

توصیه می کنیم برای جلوگیری از ارسال کل نمونه های شی یا مقادیر حساس به کلاینت، از API های taint متعلق به React یعنی taintObjectReference (opens in a new tab) و taintUniqueValue (opens in a new tab) استفاده کنید.

برای فعال کردن taint در برنامه خود، گزینه experimental.taint در پیکربندی Next.js را روی true تنظیم کنید:

next.config.js
module.exports = {
	experimental: {
		taint: true,
	},
}

سپس شیء یا مقداری را که می خواهید taint کنید به توابع experimental_taintObjectReference یا experimental_taintUniqueValue پاس دهید.

app/utils.ts
import { queryDataFromDB } from './api'
import {
	experimental_taintObjectReference,
	experimental_taintUniqueValue,
} from 'react'
 
export async function getUserData() {
	const data = await queryDataFromDB()
	experimental_taintObjectReference(
		'Do not pass the whole user object to the client',
		data
	)
	experimental_taintUniqueValue(
		"Do not pass the user's address to the client",
		data,
		data.address
	)
	return data
}
app/page.tsx
import { getUserData } from './data'
 
export async function Page() {
	const userData = getUserData()
	return (
		<ClientComponent
			user={userData} // this will cause an error because of taintObjectReference
			address={userData.address} // this will cause an error because of taintUniqueValue
		/>
	)
}

برای اطلاعات بیشتر در این مورد به این لینک : امنیت و اکشن های سرور (opens in a new tab) مراجعه کنید.