استریمینگ خارج از ترتیب در ریاکت چطور کار میکنه؟
خلاصهٔ کاملتر
یه سوءتفاهم رایج اینه که استریمینگ در ریاکت با Server Components اومده، ولی واقعیت اینه که ریاکت ۱۸ از قبل هم renderToPipeableStream() و renderToReadableStream() رو داشت. مرورگرها هم به صورت native از استریم HTML پشتیبانی میکنن. اما چیزی که React Server Components و Suspense رو متمایز میکنه، استریمینگ خارج از ترتیب هست — یعنی کامپوننتها هر وقت دادهشون آماده شد میرسن، نه به ترتیب ظاهرشون در HTML.
مشکل SSR سنتی
فرض کن یه صفحه محصول داری که سه تا درخواست دیتا میزنه: اطلاعات محصول (۵۰ms)، پیشنهادها (۸۰۰ms) و نظرات (۳۰۰ms). حتی اگه این fetchها رو موازی اجرا کنی، کل صفحه منتظر میمونه تا کندترینشون — یعنی پیشنهادها — تموم بشه. Navbar و Footer هم که اصلاً نیازی به داده ندارن، باز هم بلاک میشن.
استریمینگ معمولی (درونترتیب)
با استریمینگ ساده، کاربر لازم نیست منتظر بمونه تا ProductDetails بیاد تا Navbar رو ببینه. اما باز هم Footer بلاکه چون Recommendations هنوز لود نشده. این همون in-order streaming هست — هر کامپوننت به ترتیب HTMLاش میاد.
استریمینگ خارج از ترتیب
راهحل ریاکت اینه: Navbar و Footer رو همون اول بفرست، یه placeholder برای کامپوننتهای کند بذار، و وقتی داده آماده شد، اون placeholder رو با محتوای واقعی عوض کن. هیچ انتظاری، هیچ بلاکی. کامپوننتها هر وقت دادهشون حاضر شد میرسن. با Suspense این کار خیلی راحته:
export default function Page() {
return (
<>
<Navbar />
<Suspense fallback={<p>loading...</p>}>
<ProductDetails />
</Suspense>
<Suspense fallback={<p>loading...</p>}>
<Recommendations />
</Suspense>
<Footer />
</>
);
}پشت صحنه چی میگذره؟
وقتی HTML استریم میشه، ریاکت ابتدا یه div با fallback میفرسته و یه marker خاص برای هر Suspense boundary میذاره. مثلاً برای ProductDetails چیزی شبیه این به کلاینت میرسه:
<!--$?--><template id="B:0"></template>
<div>loading..</div>
<!--/$-->اینجا B:0 شناسهای هست که ریاکت ازش برای سوآپ استفاده میکنه. $? یعنی این Suspense boundary هنوز pending هست.
وقتی داده آماده شد
سرور کامپوننت رندرشده رو به صورت یه div مخفی با id مثل S:0 میفرسته و بلافاصله بعدش یه تابع کوچیک JavaScript اجرا میشه:
<div hidden id="S:0">ProductDetails</div>
<script>$RC("B:0", "S:0")</script>تابع $RC قبلاً توی استریم فرستاده شده بود. وقتی اجرا میشه، placeholder با id="B:0" رو پیدا میکنه و محتوای S:0 رو جاش میذاره. DOM به عنوان یه staging area عمل میکنه.
یه نکته امنیتی جالب
اگه یه div با id="B:0" دستی داخل markup بذاری، $RC("B:0", "S:0") اونو پیدا میکنه و سوآپ میکنه — نه placeholder واقعی ریاکت رو! این نشون میده که ریاکت صرفاً از DOM API استاندارد استفاده میکنه و هیچ منطق خاصی برای تشخیص «درست» بودن المان نداره.
چرا این متفاوته؟
HTML parsing به صورت sequential هست، پس استریم معمولی مجبوره in-order باشه. ریاکت این محدودیت رو با استفاده از DOM به عنوان staging area، فرستادن کامپوننتها به صورت hidden div، و JavaScript برای placement در زمان درست دور میزنه.
نکات کلیدی:
- استریمینگ خارج از ترتیب یعنی کامپوننتها هر وقت دادهشون آماده شد به کاربر میرسن، نه به ترتیب HTML
- ریاکت از marker هایی مثل
B:0برای شناسایی placeholder وS:0برای محتوای واقعی استفاده میکنه - تابع
$RCمسئول سوآپ کردن placeholder با محتوای نهایی روی کلاینته - Suspense کافیه — ریاکت بقیه کارها رو خودش مدیریت میکنه
- DOM به عنوان staging area عمل میکنه؛ کامپوننتها اول hidden پارک میشن، بعد جابهجا میشن




