همانطور که میدانید نسخه ۱۸ React منتشر شده است و اکنون از طریق npm در دسترس است. در این مقاله قصد داریم تا مفاهیم جدیدی که در این نسخه معرفی شدهاند را مورد بررسی قرار دهیم.
آخرین نسخه اصلی React شامل بهبودهای خارج از برنامه مانند batching اتوماتیک، APIهای جدید مانند startTransition و رندر سمت سرور(SSR) با پشتیبانی از Suspense بوده است.
بسیاری از ویژگیهای React 18 بر روی رندر concurrent جدید ساخته شدهاند، یک تغییر پسزمینه که امکان استفاده از قابلیتهای جدید قدرتمندی را فراهم میکند. concurrent در React یک انتخاب است، یعنی فقط زمانی فعال میشود که از یک ویژگی concurrent استفاده کنید. اما اینطور به نظر میرسد که این موضوع تأثیر زیادی بر نحوه ساخت برنامههای کاربردی خواهد داشت.
تابستان گذشته، کارگروه React 18 تشکیل شد تا بازخورد متخصصان جامعهای که از آن استفاده میکنند را جمعآوری کند و تجربیات آنها را برای ارتقای React بهکار گیرند.
در صورتی که از جزئیات آن بیاطلاع هستید، تیم توسعهدهنده بسیاری از این دیدگاه را در React Conf 2021 به اشتراک گذاشتهاند:
در ادامه یک نمای کلی از آنچه که در این نسخه باید انتظارش داشته باشیم، آماده شده است که با رندر concurrent شروع میکنیم.
نکته برای کاربران React Native: نسخه React 18 در React Native با معماری جدید React Native عرضه خواهد شد. برای اطلاعات بیشتر، سخنرانی اصلی را اینجا ببینید.
مهمترین موردی که در React 18 اضافه شده است مفهوم concurrency میباشد. این موضوع تا حد زیادی برای توسعهدهندگان اپلیکیشن صادق است، اگرچه ممکن است این نکته برای نگهداری کتابخانه کمی پیچیدهتر باشد.
concurrency به خودی خود یک ویژگی نیست. این مفهوم یک مکانیسم جدید پسزمینه است که React را قادر میسازد تا چندین نسخه از UI را همزمان آماده کند. شما میتوانید concurrency را به عنوان یک جزئیات پیادهسازی در نظر بگیرید، این موضوع به دلیل اینکه چندین ویژگی را قابل استفاده میکند، ارزشمند است. همچنین React از تکنیکهای پیچیده، مانند صفهای اولویتدار و بافر چندگانه در پیادهسازی داخلی خود استفاده میکند. اما این مفاهیم را در هیچ کجای APIهای عمومی نخواهید دید.
هنگامی طراحی API سعی شده است تا جزئیات پیادهسازی از دید توسعهدهندگان پنهان باشد. بهعنوان یک توسعهدهنده React، سعی میکنید روی چیزی که میخواهید تجربه کاربری شبیه به آن باشد تمرکز کنید و React نحوه ارائه آن تجربه را مدیریت میکند. بنابراین انتظاری نیست که توسعهدهندگان React بدانند که concurrency چگونه کار میکند.
با این حال، concurrent در React از جزئیات پیادهسازی معمولی مهمتر است، این یک بهروزرسانی اساسی برای مدل render هسته React است. بنابراین در حالی که دانستن نحوه عملکرد concurrency خیلی مهم نیست، اما درک مفهوم آن در سطح بالا بسیار ارزشمند است.
منظور از ویژگی کلیدی concurrent در React این است که عمل رندر شدن قابل وقفه است. هنگامی که برای اولین بار پروژه خود را به نسخه React 18 ارتقا میدهید، قبل از افزودن هر ویژگی concurrent، بهروزرسانیها مانند نسخههای قبلی React در یک تراکنش منفرد، بدون وقفه و همزمان اجرا میشوند. سپس با رندر synchronous، هنگامی که بهروزرسانی شروع به رندر شدن میکند، تا زمانی که کاربر بتواند نتیجه را روی صفحه ببیند هیچ چیز نمیتواند آن را قطع کند.
البته در رندر شدن concurrent، این موضوع همیشه صادق نیست. React ممکن است رندر کردن یک بهروزرسانی را شروع کند، در اواسط مسیر مکث کند و سپس دوباره به مسیر خود ادامه دهد. حتی ممکن است یک رندر در حال پیشرفت را به طور کلی رها کند. React تضمین میکند که حتی در صورت قطع شدن رندر، رابط کاربری(UI) ثابت به نظر میرسد. برای انجام این کار، زمانی که کل درخت مورد ارزیابی قرار گرفت، منتظر میماند تا تغییرات DOM را تا پایان انجام دهد. با این قابلیت، React میتواند بدون مسدود کردن thread اصلی، صفحات جدیدی را در پسزمینه آماده کند. این بدان معناست که رابط کاربری حتی حین رندر کردن یک task بزرگ، میتواند بلافاصله به ورودی کاربر پاسخ دهد و یک تجربه کاربری(UX) روانی را ایجاد کند.
مثال دیگر state با قابلیت استفاده مجدد میباشد. concurrent درReact میتواند بخشهایی از رابط کاربری را از صفحه حذف کند، سپس هنگامی که از state قبلی استفاده مجدد میکند، آنها را دوباره اضافه کند. به عنوان مثال، زمانی که کاربر از صفحه نمایش فاصله میگیرد و پس از مدتی برمیگردد، React باید بتواند صفحه موجود را به همان state قبلی بازگرداند. تیم توسعهدهندگان در حال برنامهریزی برای اضافه کردن یک کامپوننت جدید به نام<Offscreen> در آینده نزدیک هستند که این الگو را پیاده سازی میکند. به طور مشابه، میتوانید از Offscreen برای آمادهسازی رابط کاربری جدید در پسزمینه استفاده کنید تا قبل از اینکه کاربر از آن را استفاده کند، آماده باشد.
رندر concurrent یک ابزار قدرتمند و جدید در React است و بسیاری از ویژگیهای جدید برای استفاده از آن ساخته شدهاند، از جمله Suspense، transitionها و رندر سمت سرور.
از نظر فنی، رندر concurrent یک تغییر قطعی است. از آنجایی که رندر شدن concurrent قابل وقفه است، کامپوننتها زمانی که فعال میشوند کمی متفاوت رفتار میکنند. در آزمایشهایی که انجام شده است، هزاران کامپوننت به نسخه React 18 ارتقا پیدا کرده است. چیزی که دریافت شد این بود که تقریباً تمام کامپوننتهای موجود « فقط» با رندرconcurrent و بدون هیچ تغییری کار میکنند. با این حال، برخی از آنها ممکن است نیاز به تلاشهای اضافی برای migration داشته باشند. اگرچه تغییرات معمولاً کوچک هستند، اما همچنان میتوانید آنها را با شیوه و روش خود انجام دهید. رفتار رندر جدید در React 18 فقط در قسمتهایی از برنامه شما فعال است که از ویژگیهای جدید استفاده میکنند.
استراتژی کلی ارتقا این است که برنامه خود را روی React 18 بدون ایجاد تغییرات در کدهای موجود خود اجرا کنید. سپس میتوانید به تدریج و به شیوه خود شروع به اضافه کردن ویژگیهای concurrent کنید. همچنین میتوانید در حین توسعه از <StrictMode> برای کشف اشکالات مربوط به concurrency استفاده کنید. Strict Mode بر رفتار محصول نهایی تأثیری نمیگذارد اما در طول توسعه، warningهای اضافی و توابعی که دو بار فراخوانی شدهاند و انتظار میرود فاقد قدرت باشند را ثبت میکند. البته باید به این نکته اشاره کنیم که همه مشکلات را متوجه نمیشود، اما در جلوگیری از رایجترین انواع اشتباهات موثر است.
بلافاصله پس از ارتقا به نسخه React 18 میتوانید از ویژگیهای concurrent استفاده کنید. برای مثال، میتوانید startTransition را بهکار بگیرید تا بدون مسدود کردن ورودی کاربر بین صفحهها پیمایش انجام دهید و یا DeferredValue را برای کاهش رندرهای پرهزینه مورد استفاده قرار دهید.
با این حال، در دراز مدت انتظار داریم تا راه اصلی برای افزودن concurrency به برنامههای خود استفاده از یک کتابخانه یا فریمورک که با concurrent سازگاری دارد، باشد. در بیشتر موارد، شما مستقیماً با APIهای concurrent تعامل نخواهید داشت. به عنوان مثال، بهجای اینکه توسعهدهندگان هر زمان که به صفحهای جدید میروند startTransition را فراخوانی کنند، کتابخانههای router بهطور خودکار پیمایشها را در startTransition قرار میدهند.
البته این موضوع که کتابخانهها بتوانند بهروزرسانی شده و با concurrent سازگار شوند، یک فرآیند زمانبر است و ممکن است مدتی طول بکشد. در نسخه جدید، APIهای جدیدی ارائه شده است تا کتابخانهها بتوانند از ویژگیهای concurrent استفاده کنند.
برای اطلاعات بیشتر، پست نحوه ارتقا به React 18 را ببینید.
در React 18، میتوانید از Suspense برای fetch کردن دادهها در فریمورکهایی مانند Relay، Next.js، Hydrogen یا Remix استفاده کنید. fetch کردن موقت داده با Suspense از نظر فنی امکان پذیر است، اما هنوز به عنوان یک استراتژی کلی توصیه نمیشود.
در آینده، ممکن است موارد اولیه دیگری را در معرض نمایش بگذارند که شاید بدون استفاده از فریمورک میتواند دسترسی به دادههای شما را با Suspense آسانتر کند. با این حال، Suspense زمانی بهترین عملکرد را دارد که عمیقاً با معماری برنامه شما یعنی با router ،data layer و محیط رندر سرور ادغام شود. بنابراین انتظار میرود که حتی در دراز مدت، کتابخانهها و فریمورکها نقش مهمی در اکوسیستم React ایفا کنند.
همانند نسخههای قبلی React، میتوانید از Suspense برای تقسیم کد روی کلاینت با استفاده از React.lazy نیز استفاده کنید. اما دیدگاه اصلی برای Suspense همیشه چیزی فراتر از لود کردن کد بوده است. هدف این است که پشتیبانی از Suspense را گسترش پیدا کند تا در نهایت، همان Suspense بازگشتی اعلامی بتواند هر عملیات asynchronous همانند لود کردن کد، داده، تصاویر و غیره را مدیریت کند.
Server Componentها یکی از ویژگیهای آینده است که به توسعهدهندگان اجازه میدهد تا برنامههایی بسازند که سرور و کلاینت را دربرمیگیرند و تعامل غنی برنامههای سمت کلاینت را با عملکرد بهبود یافته رندر شدن سرور به روش سنتی ترکیب میکنند.Server Componentها ذاتاً با مفهوم concurrent در React همراه نیستند، اما به گونهای طراحی شدهاند که با ویژگیهای concurrent همانند Suspense و رندر شدن سمت سرور بهترین عملکرد را داشته باشند.
البته Server Componentها هنوز آزمایشی هستند، اما انتظار میرود که نسخه اولیه آن در آپدیت آینده نسخه ۱۸ منتشر گردد. در عین حال، تیم توسعه در حال کار با فریمورکهایی مانند Next.js، Hydrogen و Remix هست تا طرح پیشنهادی را جلو ببرند و آن را برای پذیرش گسترده آماده کنند.
batching زمانی اتفاق میافتد که React برای اینکه عملکرد بهتری داشته باشد، بهروزرسانی چندین state را در یک re-render واحد گروهبندی میکند. بدون batching اتوماتیک، بهروزرسانیها فقط در کنترلکنندههای event ریاکت دستهبندی میشوند. اما بهروزرسانیهای داخل promiseها، setTimeout، کنترلکنندههای ایونت native یا هر event دیگری بهطور پیشفرض در React دستهبندی نشدهاند. یعنی با batching اتوماتیک، این بهروزرسانیها بهطور خودکار دستهبندی میشوند:
// Before: only React events were batched. setTimeout(() => { setCount(c => c + 1); setFlag(f => !f); // React will render twice, once for each state update (no batching) }, ۱۰۰۰); // After: updates inside of timeouts, promises, // native event handlers or any other event are batched. setTimeout(() => { setCount(c => c + 1); setFlag(f => !f); // React will only re-render once at the end (that's batching!) }, ۱۰۰۰);
برای اطلاعات بیشتر، پست automatic batching برای رندرهای پایینتر در React 18 را ببینید.
transition یک مفهوم جدید در React است و کمک میکند تا بین آپدیتهای ضروری و غیرضروری تمایز قایل شویم.
آپدیتهای ضروری مانند تایپ کردن، کلیک کردن، یا فشردن دکمه نیاز به پاسخ فوری دارند تا با شهود ما در مورد نحوه رفتار اشیاء فیزیکی مطابقت داشته باشد. در غیر این صورت آنها احساس «اشتباه بودن» میکنند. با این حال، transition ها متفاوت هستند زیرا کاربر انتظار ندارد تا هر مقدار میانی را روی صفحه ببیند.
به عنوان مثال، وقتی فیلتری را در منوی کشویی انتخاب میکنید، انتظار دارید که خود دکمه فیلتر بلافاصله پس از کلیک کردن پاسخ دهد. با این حال، نتایج واقعی ممکن است به طور جداگانه انتقال پیدا کند و نمایش داده شوند. یک تاخیر کوچک نامحسوس که اغلب انتظار میرود اتفاق بیفتد، و اگر قبل از رندر شدن نتایج دوباره فیلتر را عوض کنید فقط دیدن آخرین نتایج برای شما اهمیت خواهد داشت.
به طور معمول، برای داشتن بهترین تجربه کاربری، یک ورودی تک کاربر باید منجر به آپدیت ضروری و غیرضروری شود. میتوانید از startTransition API در یک event ورودی استفاده کنید تا به React اطلاع دهید که کدام آپدیتها ضروری و کدامها transitions هستند:
import {startTransition} from 'react'; // Urgent: Show what was typed setInputValue(input); // Mark any state updates inside as transitions startTransition(() => { // Transition: Show the results setSearchQuery(input); });
آپدیتهایی که در startTransition قرار میگیرند بهعنوان آپدیتهای غیرضروری تلقی میشوند و در صورت وارد شدن موارد ضروریتر، مانند کلیک یا فشردن هر یک از کلیدها، قطع میشوند. اگر یک transition توسط کاربر قطع شود (مثلاً با تایپ چند کاراکتر در یک ردیف)، React کار رندر قدیمی را که هنوز تمام نشده است، حذف میکند و فقط آخرین بهروزرسانی را ارائه میکند.
transitionها رندر concurrent را که اجازه میدهد تا بهروزرسانی قطع شود، انتخاب خواهند کرد. اگر محتوا دوباره به حالت تعلیق درآید، transitionها به React میگویند که در حین اینکه در حال رندر کردن محتوای transition در پسزمینه هستند، به نمایش محتوای فعلی نیز ادامه دهند. برای اینکه اطلاعات بیشتری در این زمینه به دست آورید به Suspense RFC مراجعه کنید.
محتوای مربوط به transitionها از این لینک قابل دسترسی است.
Suspense به شما این امکان را میدهد تا لود شدن state برای بخشی از درخت کامپوننت را، در صورتی که هنوز برای نمایش آماده نیست به صورت واضح مشخص کنید:
<Suspense fallback={<Spinner />}> <Comments /> </Suspense>
چندین سال پیش نسخه محدودی از Suspense معرفی شده است. با این حال، تنها use case پشتیبانی شده، تقسیم کد با React.lazy بود، و در هنگام رندر شدن روی سرور هیچ پشتیبانی صورت نمیگرفت.
در React 18، پشتیبانی از Suspense روی سرور اضافه شده است و قابلیتهای آن را با استفاده از ویژگیهای رندر concurrent گسترش پیدا کرده است.
Suspense در React 18 وقتی با transition API ترکیب میشود بهترین کارکرد را دارد. اگر در طول transition به حالت Suspense(تعلیق) درآیید، React از جایگزینی محتوایی که از قبل قابل مشاهده بوده است با یک نسخه بازگشتی، جلوگیری میکند. در عوض، React رندر را تا زمانی که اطلاعات کافی برای جلوگیری از لود state نامناسب بدست بیاید، به تاخیر میاندازد.
برای اطلاعات بیشتر، RFC برای Suspense در React 18 را ببینید.
در این نسخه، از این فرصت استفاده شده است تا API هایی که برای رندر شدن روی کلاینت و سرور در معرض دید قرار میگیرد، دوباره طراحی شوند. این تغییرات به کاربران این امکان را میدهد تا در عین حال که APIهای خود را به React 18 انتقال میدهند، از APIهای قدیمی در حالت React 17 استفاده کنند.
این APIهای جدید اکنون از react-dom/client اکسپورت میشوند:
در صورتی که بخواهید هنگام بازیابی React، در طول رندر شدن یا هیدراتاسیون برای logging از خطاها مطلع شوید هم createRoot و هم hydrateRoot گزینه جدیدی به نام onRecoverableError را میپذیرند که میتوانید از آن استفاده کنید. React به طور پیشفرض در مرورگرهای قدیمی از reportError یا console.error استفاده میکند.
به مطلب مربوط به React DOM Client مراجعه کنید.
این APIهای جدید اکنون از react-dom/server اکسپورت میشوند و از پخش استریم Suspense در سرور پشتیبانی کامل دارند:
به مطلب مربوط به React DOM Server مراجعه کنید
برنامهای برای آینده وجود دارد و آن این است که یک ویژگی جدیدی اضافه شود که به React این اجازه را میدهد تا با حفظ state، بخشهایی از UI را اضافه و یا حذف کند. به عنوان مثال، زمانی که کاربر برای مدتی از صفحه نمایش فاصله میگیرد و سپس دوباره برمیگردد، React باید بتواند بلافاصله صفحه قبلی که وجود داشت را نشان دهد. برای انجام این کار React باید با استفاده از همان state، کامپوننت قبلی درختان را جدا کرده(unmount) و سپس مجدداً نصب کند و به حالت قبل برگرداند(remount).
این ویژگی باعث میشود تا برنامههای React عملکرد بهتری در خارج از برنامه داشته باشد، اما مستلزم این است که کامپوننتها در برابر اثراتی که چندین بار نصب شدن و سپس جدا شدن دربر خواهد داشت، انعطافپذیر باشند. اکثر افکتها بدون هیچ تغییری کار میکنند، اما برخی از آنها فرض میکنند که فقط یک بار نصب یا نابود میشوند.
برای کمک به آشکار شدن این مشکلات، React 18 یک بررسی جدید را که فقط برای توسعه strict mode است، معرفی میکند. این بررسی بهطور خودکار هر کامپوننتی را جدا میکند و مجدداً نصب میکند، و هر زمانی که کامپوننتی برای اولین بار نصب شود، state قبلی را هنگامی که برای بار دوم نصب میشود، بازیابی میکند.
قبل از این تغییر، React کامپوننت را نصب میکرد و افکتها را ایجاد میکرد:
* React mounts the component. * Layout effects are created. * Effects are created.
با Strict Mode در React 18، ریاکت جدا کردن و نصب مجدد کامپوننت را در حالت توسعه شبیهسازی میکند:
* React mounts the component. * Layout effects are created. * Effects are created. * React simulates unmounting the component. * Layout effects are destroyed. * Effects are destroyed. * React simulates mounting the component with the previous state. * Layout effects are created. * Effects are created.
برای اطمینان از state با قابلیت استفاده مجدد، مطالب را اینجا ببینید.
useId یک هوک جدید است و در حالی که از عدم تطابق هیدراتاسیون جلوگیری میکند، برای ایجاد شناسههای منحصربهفرد در کلاینت و سرور مورد استفاده قرار میگیرد. در درجه اول این هوک برای کتابخانههای کامپوننتی که هنگام ادغام با APIهای دسترسی نیازمند شناسههای منحصربهفرد هستند، مفید است. این موضوع مشکلی را که قبلاً در React 17 و نسخههای پایینتر وجود دارد، حل میکند اما در React 18 اهمیت بیشتری دارد. چون که روش رندر سرور استریم جدید، HTML را بصورت خارج از برنامه ارائه می دهد. برای مشاهده doc اینجا کلیک کنید.
useTransition و startTransition به شما این امکان را میدهند تا برخی از بهروزرسانیهای state را به عنوان غیرضروری علامتگذاری کنید. در این صورت سایر بهروزرسانیها به طور پیشفرض ضروری در نظر گرفته میشوند. React به بهروزرسانیهای ضروری(بهعنوان مثال، بهروزرسانی یک ورودی متن) اجازه میدهد تا بهروزرسانیهای غیر ضروری را قطع کند (به عنوان مثال، ارائه فهرستی از نتایج جستجو). برای مشاهده مطالب مربوط اینجا را ببینید
useDeferredValue به شما این امکان را میدهد تا رندر مجدد یک بخش غیرضروری از درخت را به تعویق بیندازید. این شبیه به debouncing است، اما در مقایسه با آن دارای چند مزیت است. در این مورد هیچ تاخیر زمانی ثابتی وجود ندارد، بنابراین React بلافاصله پس از نمایش اولین رندر روی صفحه، رندر به تعویق افتاده را انجام میدهد. همچنین رندر معوق قابل وقفه است و ورودی کاربر را مسدود نمیکند. میتوانید مستندات مربوط به این بخش را در این لینک مشاهده کنید.
useSyncExternalStore یک هوک جدید است که به حافظههای خارجی اجازه میدهد تا از خواندن concurrent با اجباری کردن بهروزرسانیهای حافظه به صورت همگام(synchronous) پشتیبانی کنند. این هوک نیاز به useEffect را هنگام اجرا منابع داده خارجی را از بین میبرد و برای هر کتابخانهای که با state خارجی React ادغام میشود، توصیه میگردد. مطالب مربوط را اینجا ببینید.
useInsertionEffect یک هوک جدید است که به کتابخانههای CSS-in-JS اجازه میدهد تا به مشکلات عملکرد تزریق سبکها در هنگام رندر شدن بپردازند. مگر اینکه قبلاً یک کتابخانه CSS-in-JS ساخته باشید اما انتظاری وجود ندارد تا برای همیشه از آن استفاده کنید. این هوک پس از تغییرات DOM اجرا میشود، اما باید قبل از افکتهای لایه قبلی، لایه جدید را بخوانید. این هوک مشکلی را که قبلاً در React 17 و نسخههای پایینتر وجود داشت را حل میکند. اما در React 18 اهمیت بیشتری دارد زیرا React در طول رندر concurrent به مرورگر واگذاری میشود و به آن فرصتی میدهد تا لایهبندی را دوباره محاسبه کند. مستندات مربوط را میتوانید اینجا ببینید.