در انتهای سال ۲۰۲۰ نسخه ۱۷ از کتابخانه react منتشر شد که به غیر از موارد جزئی، ویژگی های جدیدی نداشت بلکه هدف اصلی آن ارتقاء و بهبود ویژگی های react در آن زمان و هموار کردن راه برای به روز رسانی های بعدی بود. در ماه ژوئن امسال تیم توسعه react یک پست را در وبلاگ خودشان منتشر کردند که نسخه ۱۸ از کتابخانه react را معرفی می کرد. در حال حاضر هنوز نسخه ۱۸ به طور رسمی منتشر نشده است اما نسخه آلفای آن در دسترس است. این به روز رسانی برخلاف نسخه ۱۷ شامل قابلیت های زیادی است بنابراین در این مقاله ویژگی های نسخه جدید ری اکت را بررسی خواهیم کرد.
تیم توسعه react برای اولین بار گروه خاصی را دستچین کرده اند تا با نسخه جدید کار کرده و تجربه خودشان را برای تیم توسعه بازگو کنند. به این گروه دستچین شده React 18 Working Group گفته می شود. در حال حاضر اگر به صفحه discussions این پکیج در گیت هاب بروید متوجه خواهید شد که سوالات و پاسخ های مختلفی در این بخش قرار دارند و توضیحاتی نیز راجع به ویژگی های نسخه جدید ری اکت داده شده است. این بخش اطلاعاتی عالی برای یادگیری react 18 را دارد چرا که گفت و گوهای Working Group به مرور زمان به صورت عمومی در دسترس همه قرار می گیرند. من در این مقاله برای هر ویژگی به گفت و گوی انجام شده در Working Group (به اختصار WG) لینک خواهم داد.
سطح مقاله: این مقاله برای توسعه دهندگان react نوشته شده است و مطالعه آن برای افراد تازه کار مشکل خواهد بود چرا که در آن مفاهیم react مانند Suspense و غیره را توضیح نمی دهیم.
همانطور که می دانید در react به روز رسانی های مختلف state در یک گروه قرار گرفته و سپس همگی با هم اجرا می شوند. این کار برای جلوگیری از re-render (بازسازی دوباره و نمایش محتوای سایت) های متعدد است که سرعت برنامه را پایین می آورد. اگر بخواهیم برای هر تغییر در state یک بار دیگر صفحه را render کنیم طبیعتا برنامه کُند خواهد شد. ما به این قابلیت batch state updates یا به روز رسانی گروهی state می گوییم.
مسئله اینجاست که این قابلیت فقط برای عناصر DOM وجود داشت بنابراین Promise ها و timeout ها و به طور کل handler های دیگر نمی توانستند از این قابلیت استفاده کنند اما از React 18 به بعد تمام به روز رسانی ها از هر نوعی گروهی می شوند و نیازی به انجام کار خاصی توسط شما نیست. یک مثال ساده از این موضوع را در کد زیر مشاهده می کنید:
const App = () => { const [firstCount, setFirstCount] = useState(0); const [secondCount, setSecondCount] = useState(0); const handleClick = () => { setFirstCount((count) => count + 1); // re-render بدون setSecondCount((count) => count + 0.5); // re-render بدون // می شود Re-render منجر به }; /* const alternativeHandleClick = () => { Promise.resolve().then(() => { setFirstCount((count) => count + 1); // می شود Re-render منجر به setSecondCount((count) => count + 0.5); // می شود Re-render منجر به }); }; */ return ( <div> <button onClick={handleClick}>Next</button> <span style={{ color: firstCount % 2 === 0 ? "blue" : "black", }} > {secondCount} </span> </div> ); };
در حالت اول متد handleClick را برای مدیریت کلیک روی دکمه خودمان تعریف کرده ایم. این متد دو کار را انجام می دهد: دو state برنامه ما را با متدهای setFirstCount و setSecondCount تغییر می دهد. در حین اجرای این دو متد هیچ re-render ای انجام نمی شود و فقط زمانی که به انتهای متد برسیم re-render انجام خواهد شد. در مقابل متد دیگری به نام alternativeHandleClick را به صورت کامنت تعریف کرده ام که یک promise را حل می کند و آنگاه باعث ایجاد re-render در هر کدام از تغییرات state می شود. در نسخه ۱۷ از react چنین کدی batch نمی شد، یعنی هر کدام از تغییرات state باعث یک re-render جداگانه می شد چرا که درون یک promise هستند اما از نسخه ۱۸ به بعد تمام این موارد batch می شوند.
طبیعتا متوجه می شوید که با انجام این کار re-render های کمتری را خواهیم داشت که باعث افزایش سرعت برنامه ما می شود. برای اطلاعات بیشتر به این گفت و گو در WG مراجعه کنید.
همانطور که می دانید در react قابلیتی به نام Strict Mode را داریم که به شما کمک می کند مشکلات برنامه خود را سریعا تشخیص دهید. شما در این حالت چندین چک و شرط را خواهید داشت و strict mode نیز آن ها را روی کامپوننت های مورد نظرتان اجرا می کند. این ویژگی در نسخه ۱۸ تغییراتی را داشته است که من آن ها را برایتان توضیح خواهم داد.
اولین تغییر از ویژگی های نسخه جدید ری اکت اضافه شدن قابلیتی به نام strict effects به strict mode است که باعث می شود effect ها دو بار فراخوانی شوند، مخصوصا زمانی که رویداد های unmount و mount اتفاق می افتند. این مسئله باعث سخت گیر تر شدن strict mode بوده و از نظر صحت و سلامت کامپوننت ها اطمینان بیشتری به شما می دهد. به زبان ساده تر زمانی که شما یک کامپوننت را درون Strict Effects قرار بدهید، react از عمد side effect های آن کامپوننت را دو بار اجرا می کند تا متوجه رفتار های غیر عادی بشود. چرا؟ به دلیل اینکه توسعه دهندگان react همیشه در هنگام استفاده از هوک useEffect در چرخه های زندگی مانند mount و unmount از این مسئله شکایت داشته اند.
همچنین از دیگر ویژگی های نسخه جدید ری اکت این است که قابلیت دیگری به نام Offscreen API نیز معرفی شده است که به جای unmount کردن عناصر آن ها را از صفحه مخفی می کند، state آن ها را حفظ می کند و حتی effect های مربوط به mount/unmount را نیز اجرا می کند بدون اینکه واقعا آن کامپوننت خاص را mount/unmount کند. این کار باعث افزایش سرعت برنامه می شود. طبیعتا این API برای هر کامپوننتی مناسب نیست بلکه بیشتر برای کامپوننت هایی است که سربرگ (tab) دارند یا کامپوننت هایی که لیست های خاصی را نمایش می دهند. برای اطلاعات بیشتر به این گفت و گو در WG مراجعه کنید.
همانطور که می دانید در برنامه های react یک متد اصلی به نام render وجود دارد که کامپوننت اصلی react را نمایش می دهد. این API جدید روش جدید دسترسی به قابلیت های جدید react است اما API قبلی نیز فعلا نگه داشته می شود تا برنامه های قدیمی تر به مشکل برخورد نکنند. برای مقایسه به کد زیر توجه کنید:
import ReactDOM from "react-dom"; import App from "App"; const container = document.getElementById("app"); // روش قدیم ReactDOM.render(<App />, container); // روش جدید const root = ReactDOM.createRoot(container); root.render(<App />);
همانطور که می بینید از این به بعد باید با استفاده از تابع createRoot یک عنصر ریشه ای (root) را تعریف کرده و سپس آن را به تابع render پاس بدهید. این روش علاوه بر داشتن قابلیت های جدید، باعث تمیز تر شدن کدها و ساده تر شدن نگهداری از آن ها می شود.
از دیگر تغییرات این API می توان به تغییر متد hydrate و همچنین کالبک render اشاره کرد. دیگر هیچ کالبکی به عنوان آرگومان توسط render پذیرفته نمی شود بلکه باید از یک کالبک روی عنصر root استفاده کنید:
// بقیه کدها // روش قدیم ReactDOM.render(<App />, container, () => console.log("renderered")); // روش جدید const App = ({ callback }) => { return ( <div ref={callback}> <h1>Hello World</h1> </div> ); } const root = ReactDOM.createRoot(container); root.render(<App callback={() => console.log("renderered")} />);
همانطور که می بینید من از ref ها استفاده کرده ام و callback مورد نظر را به شکل یک prop به کامپوننت اصلی پاس داده ام.
متد hydrate نیز که قبلا یک تابع جداگانه بود حالا به یک شیء پیکربندی تبدیل شده است که از نظر عقلی صحیح تر و خواندن آن نیز راحت تر است:
// بقیه کدها // روش قدیم برای هیدراته کردن برنامه ReactDOM.hydrate(<App />, container); // روش جدید const root = ReactDOM.createRoot(container, { hydrate: true }); root.render(<App />);
برای اطلاعات بیشتر به این گفت و گوی WG مراجعه کنید.
در نسخه ۱۷ از react نام این قابلیت Concurrent Mode بود و کارش این بود که به برنامه های react کمک کند تا برای تمام کاربران با دستگاه ها و سرعت های اینترنت متفاوت تجربه ای مثبت را ایجاد کنند.
بدون شک بزرگترین تغییر در نسخه ۱۸ معرفی قابلیت concurrent rendering یا نمایش همزمان است که در اصل شکل تغییر یافته Concurrent Mode می باشد. این قابلیت در اصل مجموعه ای از قابلیت های مختلف در یک API جدید است که به شما اجازه می دهد برنامه هایی بهتر برای کاربران بسازید. من این قابلیت ها را به صورت تک به تک در این بخش توضیح خواهم داد.
یکی از ویژگی های نسخه جدید ری اکت این است که نسخه ۱۸ از react یک API جدید را معرفی کرده است که کارشان مدیریت به روز رسانی های بزرگ برای state (مثلا دریافت داده از سمت سرور یا فیلتر کردن لیست ها) است. در چنین حالت هایی تنظیم مجدد state باعث یک re-render می شود که سرعت برنامه را کمی کُند می کند. برای حل این مشکل تابعی جدید به نام startTransition معرفی شده است که کارش تعیین به روز رسانی های state به عنوان یک transition است تا مدیریت آن ها در لحظه انجام نشود و سرعت برنامه را پایین نیاورد.
یکی از بهترین مثال های استفاده برای این قابلیت زمانی است که کاربر شروع به تایپ در یک فیلد جست و جو در سایت شما می کند. مقدار آن فیلد (value متعلق به آن input) باید در لحظه به روز رسانی شود اما نتایج جست و جو که از سمت سرور می آیند طبیعتا چند لحظه طول خواهند کشید. در این حالت جست و جوی قبلی را کنسل می کنیم (اگر چنین جست و جویی توسط کاربر انجام شده باشد) و جست و جوی جدید او را به سمت سرور ارسال می کنیم:
import { startTransition } from "react"; // بقیه کدها // این به روز رسانی لحظه ای است بنابراین باید در لحظه انجام شود setInputValue(input); // این به روز رسانی تاخیری است بنابراین نیازی به انجام آن در لحظه نیست startTransition(() => { setSearchQuery(input); });
به زبان ساده تر این API به شما اجازه می دهد بین به روز رسانی های لحظه ای و به روز رسانی های دارای تاخیر تفاوت قائل شوید.
علاوه بر این متد، هوکی به نام useTransition نیز اطلاعاتی را راجع به transition به ما می دهد، مثلا آیا transition در حال انجام است یا تمام شده است:
// بقیه کدها const App = () => { // بقیه کدها const [isPending, startTransition] = useTransition({ timeoutMs: 2000 }); startTransition(() => { setSearchQuery(input); }); // بقیه کدها return ( <span> {isPending ? " Loading..." : null} {/* کدها و عملیات مورد نظر شما */} </span> ); }; // بقیه کدها
همانطورکه در کد بالا مشاهده می کنید من ۲ ثانیه را به عنوان timeout (زمان انتظار) پس از اتمام transition تعریف کرده ام. شما می توانید از این داده ها برای نمایش UI های loading (مثلا آیکون های spinner که به کاربر می گویند صفحه در حال بارگذاری است) استفاده کنید. برای دریافت اطلاعات کامل تر و مقایسه این timeout با متد جاوا اسکریپتی setTimeout به این گفت و گو در WG مراجعه کنید.
در این بخش نیز هوک جدیدی به نام useDeferredValue را داریم که یک مقدار را دریافت می کند و پس از timeout (زمان انتظار) خاصی آن را برمی گرداند:
import { useDeferredValue } from "react"; // بقیه کدها const [text, setText] = useState("text"); const deferredText = useDeferredValue(text, { timeoutMs: 2000 }); // بقیه کدها
در این کد متن text که در state با نام text ذخیره شده است پس از ۲ ثانیه به متغیر deferredText داده می شود. این هوک شباهت زیادی به transition هایی دارد که در بخش قبلی بررسی کردیم اما کاربرد خودش را دارد (مثلا در مواقعی که بخواهید رفتاری را با تاخیر به کاربر نمایش بدهید).
کامپوننت جدیدی به نام <SuspenseList> به react اضافه شده است که کارش مدیریت ترتیبی است که طی آن کامپوننت های تو در توی <Suspense> و <SuspenseList> نمایش داده می شوند. به این مثال توجه کنید:
import { Suspense, SuspenseList } from "react"; const App = () => { return ( <SuspenseList revealOrder="forwards"> <Suspense fallback={"Loading..."}> <ProfilePicture id={1} /> </Suspense> <Suspense fallback={"Loading..."}> <ProfilePicture id={2} /> </Suspense> <Suspense fallback={"Loading..."}> <ProfilePicture id={3} /> </Suspense> </SuspenseList> ); }; // بقیه کدها
در بعضی از مواقع داده های دریافت شده از سمت سرور مرتب شده نیستند و ترتیب خاصی ندارند اما ما می خواهیم آن داده ها را حتما با ترتیب خاصی نمایش بدهیم. در این حالت می توانیم با استفاده از <SuspenseList> مطمئن شویم که ترتیب نمایش این عناصر رعایت می شود.
هر SuspenseList یک prop به نام revealOrder می گیرد که می تواند سه مقدار زیر را داشته باشد:
سپس بر اساس همین مقدار شروع به نمایش عناصر درون خود می کند و این ترتیب را حفظ خواهد کرد. برای کسب اطلاعات بیشتر در این مورد می توانید به این صفحه از وب سایت رسمی react مراجعه نمایید.
عناصر Suspense در react 17 نیز به صورت ناپایدار و آزمایشی وجود داشته اند اما در نسخه ۱۸ بهبود پیدا کرده اند و بالاخره به حالت پایدار رسیده اند. این عناصر از نظر معماری تغییرات زیادی داشته اند و نامشان نیز از این به بعد به Concurrent Suspense تغییر پیدا کرده است.
بزرگترین تغییر ایجاد شده در این زمینه مربوط به نحوه نمایش عناصر خواهر عناصر suspend شده است:
// بقیه کدها const App = () => { return ( <Suspense fallback={<Loading />}> <SuspendedComponent /> <Sibling /> </Suspense> ); }; // بقیه کدها
در کد بالا کامپوننت SuspendedComponent همان کامپوننتی است که باید suspend شود (به همین خاطر است که درون <suspense> قرار گرفته است) و کامپوننت sibling نیز یک کامپوننت فرضی در کنار آن است. به کامپوننت هایی که در کنار هم باشند کامپوننت های خواهری یا برادری می گوییم.
در نسخه ۱۷ از react کد بالا کامپوننت Sibling را سریعا mount کرده و effect هایش را نیز صدا می زد و در نهایت سریعا آن را مخفی می کرد. طبیعتا این رفتار مشکل ساز است. این مشکل در نسخه ۱۸ حل شده است چرا که از این به بعد منتظر SuspendedComponent می مانیم تا کامل شود و سپس کامپوننت های خواهر و برادری (مانند Sibling) را mount می کنیم.
برای اطلاعات بیشتر به این گفت و گو در WG مراجعه کنید.
یکی دیگر از ویژگی های نسخه جدید ری اکت تغییرات بزرگ مربوط به Suspense Server-Side-Rendering می باشد. این تغییر به ما اجازه می دهد به دو قابلیت جدید دسترسی داشته باشیم: استفاده از <Suspense> و ()React.lazy در کنار استریم کردن در سمت سرور کدهای HTML (SSR streaming) و همچنین شخصی سازی hydration تا برنامه را با سریع ترین سرعت hydrate کنیم.
یکی از مشکلات همیشگی render کردن محتوا با react در سمت سرور این بود که اگر بخواهید از lazy و <suspense> با هم استفاده کنید به مشکلات مختلفی برخورد می کردید. از این به بعد نیازی نیست بین تقسیم کدها (code splitting) و SSR انتخاب کنید. همچنین react از این به بعد با استفاده از Suspense بخش های HTML مختلف برنامه را به صورت قسمت های بصری تولید می کند. در نهایت باید گفت که در نسخه های قبلی مانند نسخه ۱۷ از react SSR باید تمام صفحه را بارگذاری می کرد تا بتواند hydrate کردن آن را شروع کند اما در نسخه ۱۸ با استفاده از suspense عملیات hydrate کردن صفحات را به صورت قسمت به قسمت انجام می دهد که باعث بالا رفتن سرعت برنامه می شود.
نکته بعدی اینجاست که در گذشته اگر سعی می کردید <suspense> را در سمت سرور render کنید یک خطای کامل پرتاب می شد اما این مشکل در نسخه ۱۸ حل شده است و از این به بعد به جای پرتاب خطا، به صورت خودکار fallback مشخص شده را اجرا می کند (به کلاینت اجازه می دهد خودش محتوا را render کند). از آنجایی که render کردن <Suspense> قبلا در سرور ممکن نبود این به روز رسانی نباید برای کدهای قدیمی هیچ مشکلی ایجاد کند.
تغییرات معماری در suspense زیاد اتفاق افتاده اند و هنوز هم دستخوش تغییر هستند بنابراین نمی توان نظر قطعی در موردشان داد. در عین حال برای یادگیری بیشتر درباره تغییرات معماری suspense به این گفت و گو و برای یادگیری بیشتر درباره تغییرات کلی در suspense به این گفت و گوی WG مراجعه کنید.
منبع: وب سایت openreplay
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.