هیدریشن ناهمزمان در Preact و ماجرای Hydration 2.0
خلاصهٔ کاملتر
تو این پست، جووی دکروک (Jovi De Croock) از تیم Preact برمیگرده سراغ موضوع هیدریشن. یادآوری میکنه که هیدریشن یه فرایند همزمانه: Preact اچتیامال دریافتی از سرور رو با نمایش جاوااسکریپتی Virtual DOM مقایسه میکنه، هر نود DOM رو میگرده و هندلرها، refها و بقیهی جزئیات رو اعمال میکنه؛ اگه جایی ناهماهنگی ببینه، کل هیدریشن رو رها میکنه و اسمش رو میذاره mismatch.
نویسنده میگه مشکل همزمان بودن اینه که ماجرا همهیاهیچه: تا وقتی هر تکه جاوااسکریپت و دادهای که درخت لازم داره نرسه و اجرا نشه، هیچچیز صفحه تعاملی نیست. روی درختهای بزرگ، خود پاس هیدریشن هم اونقدر thread اصلی رو میگیره که یه کلیک بیجواب میمونه. این رفتار معمولاً یه محدودیت ذاتی Preact قلمداد میشه، ولی حرف اصلی پست اینه که resumed hydration از همین حالا اجازه میده مرزهای Suspense مستقل از هم هیدریت بشن.
سمت سرور، بهجای renderToString از renderToStringAsync استفاده میشه که هر نود معلق رو await میکنه و از هر تکهی حلشده ادامهی HTML رو میسازه. سمت کلاینت هم وقتی یه فرزند suspend میکنه، Preact هیدریشن اون زیردرخت رو نگه میداره، جای توقفش رو یادش میمونه و بقیهی اپ رو ادامه میده؛ بعد که داده یا کد رسید، برمیگرده و همون زیردرخت رو تعاملی میکنه. این پشتیبانی از طریق پکیجهای compat، preact-suspense و روتر preact-iso در دسترسه.
سود عملیش دو چیزه: تا وقتی یه مرز منتظر کد یا دادهست، مرورگر میتونه رندر کنه و به ورودی کاربر جواب بده بهجای اینکه وقتش رو صرف هیدریت کردن زیردرختی کنه که آماده نیست؛ و ضمناً محتوای رندرشدهی سرور همچنان دیده میشه و با fallback جایگزین نمیشه. مقایسهش با React هم به گفتهی نویسنده تفاوت اصلیش تو اولویتبندیه: React با سیگنال تعامل، اول مرزی رو هیدریت میکنه که کاربر روش کلیک کرده، ولی Preact فعلاً به ترتیب حل شدن کد یا داده پیش میره.
حالا Hydration 2.0 چی رو عوض میکنه؟ الگوریتم قدیمی فقط یک نود DOM رو بهعنوان نقطهی ادامه به خاطر میسپرد، یعنی فرض میکرد هر VNode معلق دقیقاً یه نود DOM تولید میکنه. این فرض وقتی میشکنه که کامپوننت null برگردونه (هیچ DOM) یا یه Fragment با دو المان ریشه باشه (چند نود) — نتیجهش mismatch، بازسازی DOM، همسایههای تکراری یا از دست رفتن state بود.
راهحل اینه که renderToStringAsync دور DOM تولیدشده توسط هر VNode معلق، نشانههای باز و بسته بذاره. دو نشانهی چسبیده یعنی اون زیردرخت اصلاً DOM تولید نکرده، و بین دو نشانه میتونه یک یا چند نود بشینه؛ پس کلاینت قبل از ادامه دادن، محدودهی کامل متعلق به اون زیردرخت رو میشناسه. نویسنده تأکید میکنه Hydration 2.0 هیدریشن ناهمزمان رو اختراع نمیکنه، فقط چیزی که هست رو قابلاعتماد میکنه و بخشی از Preact 11 خواهد بود.
یه مسئلهی حلنشده هم مونده: پایداری useId(). شناسهها از موقعیت تو درخت مشتق میشن و باید بین سرور و کلاینت یکی باشن، چون چیزهایی مثل aria-describedby تو HTML سرور بهشون اشاره میکنن. وقتی همسایههای معلق روی کلاینت با ترتیب متفاوتی حل بشن، شناسهها جابهجا میشن. یه اصلاحیه براش نوشتن ولی مجبور شدن ریورتش کنن و الان نویسنده به این فکر میکنه که یا کاربر برای مرز Suspense اسم بذاره یا از موقعیت درخت مشتق بشه و همون بهعنوان modifier استفاده بشه.
قدم بعدی استریمینگه؛ همون کاری که React میکنه: بهجای صبر کردن تا حل شدن همهچیز روی سرور، شل و fallbackها و تکههای HTML بهمحض آماده شدن فرستاده میشن. نویسنده میگه این رویکرد واقعاً حالتهای عجیب کمتری میسازه و کار روی هیدریت کردن درست این تکهها هم برای Preact 11 شروع شده. مزیت ملموسش اینه که کاربر خیلی زودتر چیزی روی صفحه میبینه، چون سرور میتونه شل و fallback رو زودتر flush کنه.
نکات کلیدی:
- resumed hydration از الان تو Preact هست و مرزهای Suspense رو مستقل تعاملی میکنه
- سمت سرور renderToStringAsync جای renderToString رو میگیره
- Hydration 2.0 با نشانهگذاری در HTML، محدودهی دقیق DOM هر زیردرخت رو مشخص میکنه
- تفاوت با React: اولویتبندی بر اساس تعامل کاربر، در برابر ترتیب حل شدن داده
- پایداری useId بین سرور و کلاینت هنوز یه مسئلهی بازه
- استریمینگ هم برای Preact 11 در دست کاره




