توسعه فرانت‌اند

React Server Components: معماری جدید React

تفاوت RSC با SSR، HTML و Payload، مرز use client، داده عمومی و Cache؛ نمونه کامل Next.js با صفحه Server و انتخاب تعاملی Client.

1404/11/10 3 دقیقه مطالعه 1,514 بازدید

React Server Components یا RSC اجازه می‌دهند بعضی Componentها در محیطی جدا از برنامه مرورگر اجرا شوند و کد خود آن بخش‌ها وارد Bundle کلاینت نشود. این معماری با «همه صفحه فقط HTML است» یا «هیچ JavaScript به مرورگر نمی‌رود» یکسان نیست. بخش تعاملی همچنان به کد مرورگر نیاز دارد و Framework اطلاعات لازم برای ترکیب درخت را منتقل می‌کند. مرز میان بخش داده‌محور و بخش تعاملی، تصمیم اصلی طراحی برنامه است.

برای مثال فرضی، صفحه فهرست محصولات می‌تواند داده و توضیحات را در بخش Server آماده کند و انتخاب محلی کاربر را در یک Client Component نگه دارد. لازم نیست برای نمایش چند محصول ثابت، کتابخانه پردازش داده را همراه صفحه به مرورگر بفرستیم. اما تعداد انتخاب‌شده با کلیک کاربر تغییر می‌کند و به State نیاز دارد. RSC این دو مسئولیت را جدا می‌کند؛ درباره کیفیت Query، مجوز دسترسی یا درستی قیمت به طور خودکار تصمیم نمی‌گیرد.

Server Component با SSR یک مفهوم نیست

مستندات React توضیح می‌دهد Server Component ممکن است هنگام Build یا برای درخواست در محیط Server اجرا شود. بنابراین داشتن این Component به تنهایی به معنی اجرای تازه در هر درخواست نیست. SSR معمولاً درباره تولید HTML اولیه برای نمایش صفحه است، در حالی که RSC درباره محل اجرای Component و مرز کد آن با Client است. این دو می‌توانند در یک Framework کنار هم استفاده شوند و نباید به جای یکدیگر نام‌گذاری شوند.

در Next.js، نتیجه Server Component به RSC Payload تبدیل می‌شود و همراه اطلاعات Client Componentها در ساخت HTML اولیه نقش دارد. سپس JavaScript بخش Client تعامل را فعال می‌کند. راهنمای Server و Client در Next.js این مسیر را توضیح می‌دهد. Payload فقط HTML نیست؛ نتیجه درخت، ارجاع بخش‌های Client و Props مربوط را در بر می‌گیرد. در ناوبری بعدی نیز Framework می‌تواند همین داده را برای ترکیب رابط استفاده کند.

مرز use client را کوچک و روشن نگه دارید

در App Router، صفحه و Layout به صورت پیش‌فرض Server Component هستند. این پیش‌فرض را به همه پروژه‌های React تعمیم ندهید؛ یک برنامه معمولی Vite بدون سازوکار RSC همان رفتار را ندارد. use client در ابتدای یک ماژول، مرز بخش Client را مشخص می‌کند. State تعاملی، Event Handler و دسترسی به API مرورگر از دلیل‌های متداول این مرز هستند. لازم نیست هر فرزند تعاملی دوباره همان Directive را داشته باشد اگر در گراف ماژول Client قرار گرفته است.

قرار دادن use client روی بخش بالای درخت می‌تواند واردات بیشتری را به سمت Client ببرد. بهتر است نیاز تعامل را نزدیک همان جزء مشخص کنید؛ مثلاً دکمه انتخاب در یک فایل جدا باشد و توضیح محصول Server بماند. با این حال، «هرچه مرز کوچک‌تر بهتر» قاعده بی‌قید نیست. ارتباط State، Context و تجربه تعامل باید روشن باشد. تقسیم بیش از حد جزءها ممکن است قرارداد Props را پیچیده کند و خواندن برنامه را بدون سود مشخص دشوارتر سازد.

داده عبوری را عمومی و قابل انتقال فرض کنید

Propsی که از Server به Client می‌فرستید باید با قواعد انتقال React سازگار باشند. تابع معمولی یا شیء دارای رفتار خاص را مثل داده ساده فرض نکنید. مهم‌تر اینکه داده عبوری می‌تواند در خروجی قابل دسترسی کاربر باشد. مخفی ماندن کد Server به معنی مخفی ماندن داده‌ای که خودتان به Client یا متن صفحه می‌فرستید نیست. رمز، Token سرویس و اطلاعات داخلی نباید برای راحتی مصرف در یک object بزرگ همراه داده عمومی منتقل شوند.

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

نمونه کامل با صفحه Server و انتخاب Client

برای نمونه آموزشی، یک پوشه جدا بسازید. Node.js نسخه 20.9 یا بالاتر پیش‌نیاز مسیر Next.js مورد استفاده است. فرمان‌های زیر وابستگی‌های Next 16 و React 19 را نصب می‌کنند؛ Lockfile ایجادشده نسخه‌های دقیق همان نصب را ثبت می‌کند. پس از ساخت پوشه app و سه فایل معرفی‌شده، فرمان npx next dev را اجرا و http://localhost:3000 را باز کنید. این مسیر نصب دستی مطابق راهنمای نصب Next.js است؛ نمونه به API یا پایگاه داده بیرونی نیاز ندارد.

npm init -y
npm install next@16 react@19 react-dom@19

فایل app/layout.js ساختار پایه HTML و جهت فارسی را مشخص می‌کند. وجود Layout ریشه برای این نمونه لازم است. محتوا در children قرار می‌گیرد و همان صفحه زیر این قالب نمایش داده می‌شود. Style کوتاه روی body فقط برای خوانایی نمونه است و سیستم طراحی محصول کامل محسوب نمی‌شود. به دلیل نبود تعامل در Layout، نیازی به use client در این فایل نداریم.

export default function RootLayout({ children }) {
  return <html lang="fa" dir="rtl"><body style={{fontFamily:'Tahoma, sans-serif',lineHeight:1.8,maxWidth:'48rem',margin:'2rem auto',padding:'1rem'}}>{children}</body></html>;
}

فایل app/page.js یک Server Component است. داده آموزشی به شکل آرایه محلی آماده می‌شود و فهرست نام‌ها را نمایش می‌دهد. عنوان «داده نمونه» روشن می‌کند این اقلام از موجودی فروشگاه واقعی نیامده‌اند. سپس فقط نام‌ها به Selector فرستاده می‌شوند. تابع async اینجا برای نشان دادن امکان آماده‌سازی داده است و Promise فوری، هزینه شبکه واقعی را شبیه‌سازی نمی‌کند. از زمان اجرای این نمونه، نتیجه‌ای درباره سرعت اتصال به دیتابیس استخراج نکنید.

import Selector from './Selector';
async function getDemoProducts() {
  return [{ id:'demo-1',name:'دفتر نمونه' },{ id:'demo-2',name:'مداد نمونه' }];
}
export default async function Page() {
  const products=await getDemoProducts();
  return <main><h1>داده نمونه Server</h1><ul>{products.map(product=><li key={product.id}>{product.name}</li>)}</ul><Selector names={products.map(product=>product.name)}/></main>;
}

فایل app/Selector.js مرز Client است. کاربر یکی از نام‌ها را انتخاب می‌کند و State محلی پیام را تغییر می‌دهد. این انتخاب ثبت سفارش یا ارسال به Server نیست و در Refresh باقی نمی‌ماند. کد مربوط به useState و onClick در بخش Client قرار دارد، در حالی که فهرست توضیحی صفحه در Server نوشته شده است. نام فایل import و مسیر آن با فایل واقعی یکسان است؛ اگر نام را تغییر دادید، ارجاع صفحه را هم اصلاح کنید.

'use client';
import { useState } from 'react';
export default function Selector({ names }) {
  const [selected,setSelected]=useState(null);
  return <section aria-labelledby="selector-heading"><h2 id="selector-heading">انتخاب محلی Client</h2><p>این انتخاب به سرور ارسال نمی‌شود.</p>{names.map(name=><button key={name} type="button" onClick={()=>setSelected(name)} style={{font:'inherit',margin:'0.3rem',padding:'0.5rem'}}>{name}</button>)}<p role="status">{selected ? `انتخاب فعلی: ${selected}` : 'هنوز انتخابی انجام نشده است.'}</p></section>;
}

پس از نصب، npm و Lockfile را همراه پروژه آموزشی نگه دارید. برای Build محصولی می‌توان npx next build و سپس npx next start را در همان پوشه اجرا کرد؛ خطای Build باید پیش از استفاده از خروجی حل شود. این دستورها روش اجرای نمونه‌اند و در این کار اجرا نشده‌اند. در پروژه موجود، نسخه، تنظیمات Cache و روش استقرار خود پروژه تعیین‌کننده‌اند. کپی سه فایل به یک برنامه با ساختار متفاوت بدون هماهنگی مسیرها، نمونه مستقل حاضر را بازتولید نمی‌کند.

دسترسی داده به معنای حذف مجوز نیست

Server Component می‌تواند در محیط مناسب به منبع داده سمت Server دسترسی داشته باشد، اما هر Query باید حدود کاربر و محصول را رعایت کند. دریافت مستقیم داده، جای بررسی هویت و مجوز را نمی‌گیرد. اگر صفحه خصوصی است، خواندن یک رکورد با شناسه URL بدون بررسی مالکیت کافی نیست. منطق حساس را در لایه داده با مسئولیت روشن نگه دارید تا انتقال یا تغییر Component به حذف ناخواسته کنترل دسترسی منجر نشود.

Server Function یا Server Action نیز با خود Server Component یک مفهوم نیست. دریافت ورودی و تغییر داده مسیر جداگانه‌ای دارد و باید اعتبارسنجی، مجوز و مدیریت خطا داشته باشد. یک دکمه Client که به عملی روی Server وصل می‌شود باید وضعیت ارسال و نتیجه را روشن کند. مقاله طراحی ورود و فرم مرحله‌ای برای وضعیت‌های کاربر مفیدند. معماری رندر، به تنهایی تجربه ثبت یا بازیابی را طراحی نمی‌کند.

Cache، انتظار و خطا را به تجربه وصل کنید

تازگی داده تابع سیاست Fetch و Cache و رفتار Framework است. «Server بودن» تضمین نمی‌کند کاربر تازه‌ترین مقدار را در هر لحظه ببیند. برای قیمت، موجودی و داده شخصی نیازها متفاوت‌اند. قبل از انتخاب سیاست، مشخص کنید چه داده‌ای قابل Cache است، چه چیزی با کاربر تغییر می‌کند و پس از تغییر چگونه به‌روزرسانی می‌شود. مقدار نمایش‌داده‌شده برای راهنمایی با مقدار تأییدشده هنگام تراکنش هم همیشه یک سطح تعهد ندارد.

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

هزینه و نگهداری معماری را بسنجید

کاهش Bundle بخش Server می‌تواند مفید باشد، اما اندازه Payload، زمان Query، ناوبری و Hydration بخش Client هم هزینه دارند. انتقال همه کار به Server، تأخیر شبکه و ظرفیت زیرساخت را حذف نمی‌کند. مقاله بهینه‌سازی React درباره اندازه‌گیری مسیر کند مفید است. انتخاب RSC باید با نوع داده و تعامل توضیح داده شود، نه با این فرض که هر صفحه جدید حتماً باید همان معماری را داشته باشد.

React میان ثبات Server Components در React 19 و APIهای داخلی ساخت Framework یا Bundler تمایز می‌گذارد؛ APIهای داخلی الزاماً از Semver معمول پیروی نمی‌کنند. این نکته برای سازنده زیرساخت مهم است و نباید به ناپایدار بودن همه استفاده‌های محصولی تعبیر شود. برای تیم برنامه، نسخه‌های سازگار، مرز Server و Client و مدل داده عبوری را ثبت کنید. تحویل خوب روشن می‌کند هر بخش کجا اجرا می‌شود، چه داده‌ای خارج می‌شود و کاربر در انتظار یا شکست چه می‌بیند.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!