نحوه آپدیت به نسخه React 18

همانطور که در پست قبلی صحبت کردیم در React 18 ویژگی‌هایی معرفی شده است که توسط رندر شدن concurrent جدید، با یک استراتژی پذیرش تدریجی و آهسته برای برنامه‌های موجود ارائه می‌شود. در این پست قصد داریم تا درمورد مراحل و نحوه آپدیت به نسخه React 18 صحبت کنیم.

نکته برای کاربران React Native: ری‌اکت ۱۸ در نسخه بعدی React Native عرضه خواهد شد. این موضوع به این دلیل است که React 18 برای بهره‌مندی از قابلیت‌های جدید ارائه شده در این پست به معماری جدید React Native وابسته است. برای اطلاعات بیشتر، سخنرانی اصلی React Conf را ببینید.

آموزش نحوه نصب

برای نصب آخرین آپدیت، یعنی نسخه ۱۸ React از کد زیر استفاده می‌کنیم:

npm install react react-dom

اما اگر از yarn استفاده می‌کنیم به این شکل خواهد بود:

yarn add react react-dom

به‌روزرسانی‌های APIهای رندر شدن کلاینت

هنگامی که برای اولین بار React 18 را نصب می‌کنیم، یک اخطار در کنسول خواهیم دید:

ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it’s running React 17. Learn more: https://reactjs.org/link/switch-to-createroot

در این آپدیت، React 18 یک root API جدید معرفی می‌کند که ارگونومی بهتری برای مدیریت rootها ارائه می‌دهد. root API جدید همچنین رندر concurrent را فعال می‌کند که این امکان را به ما می‌دهد تا ویژگی‌های concurrent را انتخاب کنیم.

// Before
import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);

// After
import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App tab="home" />);

همچنین unmountComponentAtNode به root.unmount تغییر یافته است:

// Before
unmountComponentAtNode(container);

// After
root.unmount();

همینطور callback از رندر حذف شده است، زیرا معمولاً هنگام استفاده از Suspense نتیجه مورد انتظار را ندارد:

// Before
const container = document.getElementById('app');
ReactDOM.render(<App tab="home" />, container, () => {
  console.log('rendered');
});

// After
function AppWithCallbackAfterRender() {
  useEffect(() => {
    console.log('rendered');
  });

  return <App tab="home" />
}

const container = document.getElementById('app');
const root = ReactDOM.createRoot(container);
root.render(<AppWithCallbackAfterRender />);

درنهایت، اگر برنامه ما از رندر شدن سمت سرور با هیدراتاسیون استفاده می‌کند، باید hydrate را به hydrateRoot ارتقا دهیم:

// Before
import { hydrate } from 'react-dom';
const container = document.getElementById('app');
hydrate(<App tab="home" />, container);

// After
import { hydrateRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = hydrateRoot(container, <App tab="home" />);
// Unlike with createRoot, you don't need a separate root.render() call here.

برای اینکه اطلاعات بیشتری در این زمینه بدست بیاورید، می‌توانید صحبت‌های کارگروه را اینجا ببینید.

به‌روزرسانی‌های APIهای رندرشدن سرور

در نسخه React 18، ما APIهای react-dom/server خود را برای پشتیبانی کامل از Suspense در سرور و استریم SSR اصلاح می‌کنیم. به عنوان بخشی از این تغییرات، API استریم Node قدیمی را که از پخش استریم Suspense افزایشی در سرور پشتیبانی نمی‌کند، منسوخ می‌کنیم.

هنگام استفاده از این API اخطاری خواهیم داشت:

  • renderToNodeStream: منسوخ شده

در عوض، برای پخش استریم در محیط‌های Node، از API زیر استفاده می‌کنیم:

  • renderToPipeableStream: جدید

همچنین در حال معرفی یک API جدید برای پشتیبانی از جریان SSR با Suspense برای محیط‌های توسعه مدرن مانند Deno و Cloudflare هستیم:

  • renderToReadableStream: جدید
  • APIهای زیر درحالی که پشتیبانی محدودی از Suspense دارند، به کار خود ادامه خواهند داد:
  • renderToString: محدود
  • renderToStaticMarkup: محدود

در نهایت، این API برای ارائه ایمیل‌ها به کار خود ادامه می‌دهد:

  • renderToStaticNodeStream

برای اینکه بیشتر در مورد تغییرات موجود در APIهای رندر سرور مطالعه کنید، پست کارگروه در مورد آپدیت به نسخه React 18 در سرور، بررسی عمیق معماری Suspense SSR جدید، و صحبت Shaundai Person در مورد پخش استریم سرور با Suspense در React Conf 2021 را ببینید.

به‌روزرسانی‌ها برای تعاریف TypeScript

اگر در پروژه خود از TypeScript استفاده می‌کنیم، باید dependencyهای @types/react و @types/react-dom خود را به آخرین نسخه‌ها به‌روزرسانی کنیم. نوع(type)‌های جدید ایمن‌تر هستند و مشکلاتی را که قبلاً توسط type checker نادیده گرفته می‌شد، برطرف می‌کنند. مهم‌ترین تغییر این است که اکنون باید هنگام تعریف propها، پراپ children بطور صریح فهرست شود، به عنوان مثال:

interface MyButtonProps {
  color: string;
  children?: React.ReactNode;
}

می‌توانید برای اطلاع از لیست کامل تغییرات نوع(type)ها، React 18 typings pull request را ببینید.

همچنین برای اینکه کدهای برنامه خود را سریع‌تر به تایپ‌های جدید و ایمن‌تر انتقال دهیم، می‌توانیم از اسکریپت انتقال خودکار برای این‌ کار استفاده کنیم.

Batching اتوماتیک

در این آپدیت، React 18 به‌طور پیش‌فرض با انجام batching بیشتر، بهبودهای عملکردی خارج از برنامه را اضافه می‌کند. batching زمانی اتفاق می‌افتد که React به‌روزرسانی‌های چند state را در یک رندر مجدد برای عملکرد بهتر گروه‌بندی می‌کند. قبل از React 18، ما فقط به‌روزرسانی‌ها را در کنترل‌کننده‌های ایونت React دسته‌بندی می‌کردیم. به‌روزرسانی‌های داخل promiseها، setTimeout، کنترل‌کننده‌های ایونت native یا هر ایونت دیگری به‌طور پیش‌فرض در React دسته‌بندی نمی‌شدند:

// Before React 18 only React events were batched

function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will render twice, once for each state update (no batching)
}, ۱۰۰۰);

برای شروع آپدیت React 18 با createRoot، همه به‌روزرسانی‌ها بدون توجه به منشا آن‌ها، به‌طور خودکار دسته‌بندی می‌شوند. این به این معنی است که به‌روزرسانی‌های داخل timeoutها، promiseها، کنترل‌کننده‌های ایونت native یا هر ایونت دیگری مانند به‌روزرسانی‌های داخل ایونت‌های React دسته‌بندی خواهند شد:

// After React 18 updates inside of timeouts, promises,
// native event handlers or any other event are batched.

function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}

setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React will only re-render once at the end (that's batching!)
}, ۱۰۰۰);

این یک تغییر اساسی است و انتظار داریم تا منجر به رندر شدن کار کمتر و در نتیجه عملکرد بهتر در برنامه‌های ما شود. برای جلوگیری از batching اتوماتیک می‌توانیم از flushSync استفاده کنیم:

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCounter(c => c + 1);
  });
  // React has updated the DOM by now
  flushSync(() => {
    setFlag(f => !f);
  });
  // React has updated the DOM by now
}

برای کسب اطلاعات بیشتر در این زمینه، Automatic batching deep dive را ببینید.

 APIهای جدید برای کتابخانه‌ها

در کارگروه آپدیت React 18، ما با نگه‌دارنده کتابخانه‌ها کار کردیم تا APIهای جدیدی را ایجاد کنیم که برای پشتیبانی از رندر concurrent برای موارد خاص در زمینه‌هایی مانند styleها و حافظه‌های خارجی مورد نیاز است. برای پشتیبانی از React 18، برخی از کتابخانه‌ها ممکن است نیاز داشته باشند تا به یکی از APIهای زیر سوئیچ کنند:

  • useSyncExternalStore یک هوک جدید است که به حافظه‌های خارجی اجازه می‌دهد تا با اجباری کردن به‌روزرسانی‌های حافظه به صورت همگام(synchronous)، از خواندن concurrent پشتیبانی کنند. این API جدید برای هر کتابخانه‌ای که با state خارجی React ادغام می‌شود، توصیه می‌گردد. در پست useSyncExternalStore و useSyncExternalStore API این هوک بطور دقیق‌تر مورد بررسی قرار گرفته است.
  • useInsertionEffect یک هوک جدید است که به کتابخانه‌های CSS-in-JS اجازه می‌دهد تا به مشکلات عملکرد تزریق سبک‌ها در رندر بپردازند. مگر اینکه قبلاً یک کتابخانه CSS-in-JS ساخته باشید اما انتظاری وجود ندارد تا برای همیشه از آن استفاده کنید. هوک useInsertionEffect پس از تغییرات DOM اجرا می‌شود، اما باید قبل از افکت‌های لایه قبلی، لایه جدید را بخوانید. این موضوع مشکلی را که قبلاً در React 17 و نسخه‌های پایین‌تر وجود داشت را حل می‌کند، اما در React 18 مهم‌تر است. زیرا React در طول رندر concurrent به مرورگر واگذار می‌شود و به آن فرصتی می‌دهد تا لایه‌بندی را دوباره محاسبه کند. برای اطلاعات بیشتر، راهنمای ارتقاء کتابخانه برای <style> را ببینید.

به‌روزرسانی‌ها به Strict Mode

در آینده، می‌خواهیم ویژگی‌ جدیدی اضافه کنیم که به React اجازه می‌دهد تا با حفظ state، بخش‌هایی از UI را اضافه و حذف کند. به عنوان مثال، زمانی که کاربر برای مدتی از صفحه نمایش دور می‌شود و به سپس برمی‌گردد، React باید بتواند بلافاصله همان صفحه قبلی موجود را نشان دهد. برای انجام این کار، React درختان را با استفاده از همان state کامپوننت قبلی جدا کرده و مجددا نصب کند.

این ویژگی عملکرد بهتری را در حالت خارج از برنامه به React می‌دهد، اما مستلزم این است که کامپوننت‌ها در برابر اثراتی که چندین بار نصب شدن و سپس جدا شدن دربر خواهد داشت، انعطاف‌پذیر باشند. اکثر افکت‌ها بدون هیچ تغییری کار می‌کنند، اما برخی از آن‌ها فرض می‌کنند که فقط یک بار نصب یا نابود می‌شوند.

برای کمک به آشکار شدن این مشکلات،React 18 یک بررسی جدید را که فقط برای توسعه Strict Mode است، معرفی می‌کند. این بررسی به‌طور خودکار هر کامپوننتی را جدا می‌کند و مجدداً نصب می‌کند، و هر زمانی که کامپوننتی برای اولین بار نصب شود، state قبلی را هنگامی که برای بار دوم نصب می‌شود، بازیابی می‌کند.

قبل از این تغییر، React کامپوننت را نصب و افکت‌ها را ایجاد می‌کرد:

* React mounts the component.
    * Layout effects are created.
    * Effect effects are created.

با Strict Mode در React 18، ری‌اکت جدا کردن و نصب مجدد کامپوننت را در حالت توسعه شبیه‌سازی می‌کند:

* React mounts the component.
    * Layout effects are created.
    * Effect 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 effect setup code runs
    * Effect setup code runs

برای اطلاعات بیشتر، به پست‌های کارگروه درمورد افزودن state با قابلیت استفاده مجدد به StrictMode و نحوه پشتیبانی از state با قابلیت استفاده مجدد در افکت‌ها مراجعه کنید.

پیکربندی محیط تست

هنگامی که برای اولین بار تست‌های خود را برای استفاده از createRoot به‌روزرسانی می‌کنیم، ممکن است این اخطار را در کنسول آزمایشی خود مشاهده کنیم:

The current testing environment is not configured to support act(…)

برای رفع این مشکل قبل از اجرای تست، globalThis.IS_REACT_ACT_ENVIRONMENT را روی true تنظیم می‌کنیم:

// In your test setup file
globalThis.IS_REACT_ACT_ENVIRONMENT = true;

هدف از flag این است تا به React بگوییم که در یک محیط شبیه به تست در حال اجرا است. حتی اگر فراموش کنیم به‌روزرسانی را با استفاده از act انجام دهیم، React اخطارهای مفیدی را ثبت خواهد کرد.

همچنین می‌توانیم مقدار flag را روی false تنظیم کنیم تا به React بگوییم که act لازم نیست. این موضوع می‌تواند برای تست‌های سرتاسری که یک محیط کامل مرورگر را شبیه‌سازی می‌کنند، مفید باشد.

در نهایت، انتظار داریم تا کتابخانه‌های تست این موضوع را به‌طور خودکار برای ما پیکربندی کنند. به عنوان مثال، نسخه بعدی React Testing Library دارای پشتیبانی داخلی از React 18 و بدون هیچ‌گونه پیکربندی اضافی است.

همینطور توضیحات بیشتر در مورد API تست act و تغییرات مرتبط در کارگروه در این لینک موجود می‌باشد.

حذف پشتیبانی از اینترنت اکسپلورر

در این نسخه، React پشتیبانی از اینترنت اکسپلورر را که در ۱۵ ژوئن ۲۰۲۲ از حالت پشتیبانی خارج می‌شود، کنار می‌گذارد. ما اکنون این تغییر را اعمال می‌کنیم زیرا ویژگی‌های جدیدی که در React 18 معرفی شده‌اند با استفاده از ویژگی‌های مرورگرهای مدرن مانند ریزتسک‌ها ساخته شده‌اند که نمی‌توانند به اندازه کافی در اینترنت اکسپلورر polyfill شوند.

اگر نیاز به پشتیبانی از اینترنت اکسپلورر داریم، توصیه می‌شود تا از React 17 استفاده کنیم.

Deprecationها

  • react-dom: اکنون ReactDOM.render کاربرد ندارد. اگر از آن استفاده کنیم اخطار خواهد داد و برنامه در حالت React 17 اجرا خواهد شد.
  • react-dom: در نسخه جدید ReactDOM.hydrate منسوخ شده است. اگر از آن استفاده کنیم اخطار خواهد داد و برنامه در حالت React 17 اجرا خواهد شد.
  • react-dom: در نسخه جدید ReactDOM.unmountComponentAtNode از بین رفته است و مورد استفاده قرار نمی‌گیرد.
  • react-dom: اکنون renderSubtreeIntoContainer کاربردی ندارد.
  • react-dom/server:در این نسخه renderToNodeStream مورد استفاده قرار نمی‌گیرد.

سایر تغییرات قطعی موجود

  • زمان‌بندی ثابت useEffect: اگر به‌روزرسانی در طول یک ایونت ورودی گسسته کاربر مانند یک کلیک و یا یک رویداد keydown فعال شده باشد، اکنون React به‌طور همگام توابع افکت را پاک می‌کند. این رفتار قبلا همیشه قابل پیش‌بینی و یا سازگار نبود.
  • خطاهای دقیق‌تر هیدراتاسیون: عدم تطابق هیدراتاسیون به دلیل مفقود شدن و یا محتوای اضافی متن، اکنون به‌جای اخطار همانند خطا در نظر گرفته می‌شود. React دیگر سعی نمی‌کند تا با قرار دادن یا حذف یک گره روی کلاینت در تلاش برای مطابقت با نشانه‌گذاری سرور، گره‌های جداگانه را اصلاح کند و به کلاینتی که تا نزدیک‌ترین مرز <Suspense> در درخت رندر می‌شود، بازمی‌گردد. این کار تضمین می‌کند که درخت هیدراته سازگار است و از حفره‌های بالقوه حریم خصوصی و امنیتی که می‌تواند ناشی از عدم تطابق هیدراتاسیون باشد، جلوگیری می‌کند.
  • درختان معلق(Suspense) همیشه ثابت هستند: اگر یک کامپوننت قبل از اینکه به‌طور کامل به درخت اضافه شود به حالت تعلیق دربیاید، React آن را به شکل ناقص به درخت اضافه نمی‌کند و یا افکت‌های آن را ایجاد نخواهد کرد. در عوض React درخت جدید را به‌طور کامل دور می‌اندازد و منتظر می‌ماند تا عملیات ناهمگام(asynchronous) به پایان برسد و سپس دوباره و از ابتدا رندر کردن را امتحان کند. React این تلاش مجدد را به صورت همگام و بدون مسدود کردن مرورگر رندر می‌کند.
  • افکت‌های لایه‌بندی با تعلیق(Suspense): وقتی درختی دوباره به حالت تعلیق درمی‌آید و سپس به حالت بازگشتی برمی‌گردد، React افکت‌های لایه‌بندی را پاک می‌کند و پس از نمایش مجدد محتوای داخل مرز، آن‌ها را دوباره ایجاد می‌کند. این موضوع مشکلی را که از اندازه‌گیری صحیح لایه‌بندی کتابخانه‌های کامپوننت‌ها هنگام استفاده با Suspense جلوگیری می‌کرد، برطرف می‌کند.
  • الزامات جدید محیط JS: ری‌اکت اکنون به ویژگی‌های مرورگرهای مدرن از جمله Promise، Symbol و assign بستگی دارد. اگر از مرورگرها و دستگاه‌های قدیمی‌تری مانند اینترنت اکسپلورر که ویژگی‌های مرورگر مدرن را به صورت native ارائه نمی‌دهند یا پیاده‌سازی‌های غیرمنطبق دارند، استفاده می‌کنیم باید در برنامه خود یک polyfill سراسری اضافه کنیم.

دیگر تغییرات قابل توجه‌‌

React

  • کامپوننت‌ها اکنون می‌توانند مقدار undefined را رندر کنند: اکنون اگر یک مقدار undefined از یک کامپوننت بازگشت پیدا کند، React دیگر اخطاری نخواهد داد. این موضوع باعث می‌شود تا مقادیر مجازی که کامپوننت آن‌ها را برمی‌گرداند با مقادیر مجاز در اواسط درخت کامپوننت سازگار باشد. پیشنهاد می‌کنیم برای جلوگیری از اشتباهاتی مانند فراموش کردن دستور بازگشت قبل از JSX از یک لینتر(linter) استفاده کنید.
  • اکنون در تست‌ها، اخطارهای act انتخابی هستند: اگر تست‌های سرتاسری را اجرا کنیم، اخطارهای act غیرضروری هستند. اکنون مکانیزمی وجود دارد که می‌توانیم آن‌ها را فقط برای تست‌های واحدی که مفید و سودمند هستند، فعال کنیم.
  • هیچ اخطاری در مورد setState در کامپوننت‌‌های نصب نشده وجود ندارد: قبلا هنگامی که setState را روی یک کامپوننت نصب نشده فراخوانی می‌کردیم، React در مورد memory leak اخطار می‌داد. این اخطار برای subscription‌ها نیز اضافه شده بود، اما مردم عمدتاً در سناریوهایی با آن مواجه می‌شوند که تنظیمات state خوب است و راه‌حل‌ها کد را بدتر می‌کنند. این اخطار اکنون حذف شده است.
  • عدم حذف لاگ‌های کنسول: وقتی از Strict Mode استفاده می‌کنیم، React هر کامپوننتی را دو بار رندر می‌کند تا در پیدا کردن side effectهای غیرمنتظره به ما کمک کند. در React 17، لاگ‌های کنسول را برای یکی از دو رندر حذف کرده بودند تا خواندن آن‌ها را آسان‌تر کنند. باتوجه به این که این موضوع کمی گیج‌کننده بود، suppression حذف شد. در عوض، اگر React DevTools را نصب کرده باشیم، رندرهای لاگ دوم به رنگ خاکستری نمایش داده می‌شوند و گزینه‌ای که به‌طور پیش‌فرض غیرفعال است، برای حذف کامل آن‌ها وجود خواهد داشت.
  • بهبود استفاده از حافظه: React اکنون فیلدهای داخلی بیشتری را در هنگام unmount پاک می‌کند و تأثیرات memory leakهای ثابت‌نشده که ممکن است در کد برنامه ما وجود داشته باشد را کاهش می‌دهد.

React DOM Server

  • renderToString: هیچ خطایی  هنگام تعلیق روی سرور اتفاق نمی‌افتد. در عوض، HTML بازگشتی را برای نزدیک‌ترین مرز <Suspense> منتشر می‌کند و سپس دوباره سعی می‌کند همان محتوا را در کلاینت رندر کند. اما همچنان توصیه می‌شود که به جای آن، به یک API استریم مانند renderToPipeableStream یا renderToReadableStream سوییچ کنیم.
  • renderToStaticMarkup: هنگام تعلیق روی سرور خطایی اتفاق نمی‌افتد. در عوض، HTML بازگشتی را برای نزدیک‌ترین مرز <Suspense> منتشر می‌کند و سپس دوباره سعی می‌کند همان محتوا را در کلاینت رندر کند.

 

منبع

دیدگاه‌ها:

افزودن دیدگاه جدید