فریز کردن کامپوننت موقع انیمیشن خروج در React با Suspense
خلاصهٔ کاملتر
نویسنده موقع ساختن کتابخونهی کامپوننتهای داشبورد Clerk به یه مشکل تکراری خورده: وقتی یه کامپوننت داره با انیمیشن بسته میشه، محتواش هم همزمان عوض میشه. مثلاً تو یه Select وقتی یه لیبل انتخاب میکنی، placeholder جستجو تغییر میکنه و لیبل انتخابشده میره بالای لیست. React این آپدیتها رو با آپدیتی که پاپاور رو میبنده یکجا batch میکنه، یعنی همه با هم اعمال میشن. نتیجه اینه که چیزی که داره محو میشه بیدلیل توجه رو به خودش جلب میکنه.
اون اول چند تا راه دیگه رو امتحان کرده. یکی این بوده که state کامپوننت رو قبل از خروج ذخیره کنه و تو انیمیشن از همون استفاده کنه، ولی به گفتهی خودش این برای بقیهی برنامهنویسها دردسر داره چون باید حواسشون به هر stateی باشه که ممکنه re-render راه بندازه. راه دیگه عقب انداختن آپدیتها تا آخر انیمیشنه، مثل prop onOpenChangeComplete تو Base UI. نویسنده میگه این وقتی انیمیشن خروج وسط کار قطع بشه پیچیده میشه.
راهحل نهایی از رفتار Suspense میاد. وقتی یه زیردرخت suspend میشه، React هنوز رندرش میکنه ولی تغییراتش رو روی DOM اعمال (commit) نمیکنه، پس اون بخش تو حالت قبلیش «یخ میزنه». تنها مشکل اینه که React همزمان با display: none !important قایمش میکنه. نویسنده یه wrapper دور Suspense ساخته که با یه Fragment ref (یعنی ref روی Fragment که به نودهای DOM بچهها دسترسی میده) نودها رو پیدا میکنه و تو useInsertionEffect اون استایل رو برمیداره. برای suspend کردن هم یه کامپوننت میذاره که روی یه Promise بیپایان React.use صدا میزنه:
return (
<React.Fragment ref={fragmentRef}>
<React.Suspense>
{frozen && <Suspend />}
{children}
</React.Suspense>
</React.Fragment>
)استفادهاش سادهست. مثلاً با React Aria Components فقط کافیه محتوای پاپاور رو داخل Freeze بذاری و isExiting رو بهش بدی:
<Popover>
{({ isExiting }) => (
<Freeze frozen={isExiting}>
<ListBox>{/* ... */}</ListBox>
</Freeze>
)}
</Popover>نویسنده میگه این روش مخصوصاً با React Aria خوب کار میکنه، چون اونجا hover و press و focus با state خود React مدیریت میشن و اونا هم فریز میشن (برخلاف :hover تو CSS). ولی خودش هم قبول داره که این یه ترفنده و ممکنه تو نسخههای بعدی React بشکنه. یه محدودیت دیگه هم داره: هر استایل inline display که المنت از قبل داشته پاک میشه. امیدواره تیم React یه روز این حالت «دیدهشدنی ولی غیرفعال» رو بهشکل یه mode رسمی برای Activity اضافه کنه.
نکات کلیدی:
- Suspense تغییرات زیردرخت suspendشده رو روی DOM اعمال نمیکنه ولی با display: none !important قایمش میکنه.
- کامپوننت Freeze اون استایل رو تو useInsertionEffect برمیداره تا محتوا دیده بشه ولی آپدیت نشه.
- برای پیدا کردن نودهای DOM از Fragment ref و متد observeUsing استفاده شده.
- تو React 18 و 19.2 تست شده، ولی رسمی نیست و ممکنه بعداً بشکنه.
- استایل inline display قبلی المنت با این پیادهسازی ساده پاک میشه.




