درود فراوان 🖐️ من امیررضا هستم. نویسندهٔ جدید دیتی. نسخه Stable ریاکت ۱۹ با کلی ویژگی جدید بلاخره منتشر شده که تو این پستِ دیتی قراره با هم ویژگیهای این نسخهٔ ریاکت رو بررسی کنیم.
- Actions (اکشن ها)
- هوک useActionState
- هوک useFormStatus
- هوک useOptimistic
- use API
- React DOM Static APIs
- React Server Components
- بهبودها در ریاکت ۱۹
- Initial Value در useDeferredValue
- پشتیبانی از متادیتاها
- پشتیبانی Stylesheet ها
- پشتیبانی از اسکریپتهای Async
- پیش بارگذاری منابع
- گزارش بهتر خطاها
۱. Actions (اکشن ها)
یکی از رایجترین و پرکاربردترین موارد در ریاکت اکشنها هستند که به ما این قابلیت و امکان رو میدن تا بتونیم درخواستهایی که میفرستیم رو مدیریت کنیم و عملیاتی رو بر اساس وضعیت درخواست انجام بدیم.
فرض کنین قراره اسم کاربر رو طی یک درخواست تغییر بدیم:
function UpdateName() { const [name, setName] = useState(""); const [error, setError] = useState(null); const [isPending, setIsPending] = useState(false); const handleSubmit = async () => { setIsPending(true); const error = await updateName(name); setIsPending(false); if (error) { setError(error); return; } }; return ( <div> <input value={name} onChange={(event) => setName(event.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> Update </button> {error && <p>{error}</p>} </div> ); }
همونطور که میبینید برای فرستادن درخواست باید چندین استیت رو به صورت همزمان بسته به وضعیت درخواست آپدیت و مدیریت کنین. اما توی ریاکت ۱۹ میتونین با استفاده از useTransition راحتتر استیتها رو مدیریت کنین.
اما useTransition چجوری کار میکنه؟ 🤔
function UpdateName() { const [name, setName] = useState(""); const [error, setError] = useState(null); const [isPending, startTransition] = useTransition(); const handleSubmit = () => { startTransition(async () => { const error = await updateName(name); if (error) { setError(error); return; } }) }; return ( <div> <input value={name} onChange={(event) => setName(event.target.value)} /> <button onClick={handleSubmit} disabled={isPending}> Update </button> {error && <p>{error}</p>} </div> ); }
همونظور که میبینید useTransition دو تا پراپرتی میگیره که اولی وضعیت یا همون isPending رو مشخص میکنه و چیزی که برمیگردونه true یا false هست. و دومین پراپرتی یک تابع Async هست و عملیات مد نظر ما (درخواستِ تغییر نام) رو توی خودش قرار میده.
useTranstition گزینه خوبی برای بهینه سازی UI در هنگام انجام عملیات سنگین و بروزرسانی میتونه باشه و به ما اجازه میده تا به شکل بهتری حالتهای Loading رو مدیریت کنیم.
۲. هوک useActionState
توی ریاکت ۱۹ فرمها میتونن اکشن بگیرن و بر اساس اونها مدیریت بشن که کار ما رو باز هم سادهتر میکنه.
نمونه کد زیر نحوه استفاده از useActionState در فرم ها هست:
function ChangeName({ name, setName }) { const [error, submitAction, isPending] = useActionState( async (previousState, formData) => { const error = await updateName(formData.get("name")); if (error) { return error; } return null; }, null, ); return ( <form action={submitAction}> <input type="text" name="name" /> <button type="submit" disabled={isPending}>Update</button> {error && <p>{error}</p>} </form> ); }
همونطور که میبینید در useActionState سه پراپرتی وجود داره که اولین پراپرتی در صورت ارور مقداردهی میشه. دومین مقدار یا submitAction تابعی است که باید به اکشن فرم بدیم تا تابع دریافتی useActionState اجرا بشه. و سومین مقدار وضعیت پردازش رو هندل میکنه.
هوک useActionState یک تابع Async میگیره و استیت قبلی و دادههای ورودی فرم رو در اختیارمون میذاره تا از اونها استفاده کنیم و با متد get بگیریمشون و پردازش روشون انجام بدیم.
⚠️ اگر در هنگام درخواست useActionState به مشکل بخورد در متغیر error ذخیره میشه.
⚠️ اینم بگم که قبلاً شما از useFormState استفاده میکردین اما الان با اپدیتی که ریاکت داده اسم اون به useActionState تغییر کرده.
۳. هوک useFormStatus
همونطور که از اسمش معلومه وضعیت یک فرم رو برمیگردونه:
import { useFormStatus } from 'react-dom'; function DesignButton() { const { pending, data, method, action } = useFormStatus(); return <button type="submit" disabled={pending} /> }
در واقع باید بگیم این هوک وضعیت رو از والدش که <form> هست میخونه و مزیتی که داره اینه که میتونیم وضعیت رو در کانتکست ذخیره کنیم و دیگه نیازی به پاس دادنش به فرزند ها نیست (از تعریف پراپ اضافی جلوگیری کنیم).
۴. هوک useOptimistic
یک pattern خفن UI در ریاکت ۱۹ معرفی شد. اما چیکار میکنه؟ 🤔
شاید خیلیهاتون وقتی میخواستین درخواستی رو بفرستین که زمان بره، باید تا نتیجه درخواست صبر میکردین و سپس UI پروژتون آپدیت بشه.
اما با استفاده کردن از این پترن ریاکت ۱۹ میتونین به راحتی قبل از ارسال درخواست UI خودتون رو با مقدار جدید بروزرسانی کنین و درخواست در پشت صحنه انجام میشه.
مثال استفاده از useOptimistic:
function ChangeName({currentName, onUpdateName}) { const [optimisticName, setOptimisticName] = useOptimistic(currentName); const submitAction = async formData => { const newName = formData.get("name"); setOptimisticName(newName); const updatedName = await updateName(newName); onUpdateName(updatedName); }; return ( <form action={submitAction}> <p>Your name is: {optimisticName}</p> <p> <label>Change Name:</label> <input type="text" name="name" disabled={currentName !== optimisticName} /> </p> </form> ); }
همونطور که میبینین useOptimistic نام فعلی رو از پراپ میگیره و بعد از اینکه فرم ثبت شد وارد تابع submitAction میشیم. نامی که کاربر در فرم وارد کرد رو میگیریم و در useOptimistic به عنوان نام جدید اون رو ست میکنیم و UI خودمون رو بروزرسانی میکنیم. سپس درخواست برای تغییر نام در سرور ارسال میشه.
۵. use API
در ریاکت ۱۹ یک api جدید اضافه شده به نام use که میتونه منابع رو در زمان رندر بخونه. مثلاً با استفاده از use میتونین یک Promise رو بخونین و تا زمانی که resolve بشه در حالت Suspend میمونه:
import { use } from 'react'; function Comments({commentsPromise}) { // `use` will suspend until the promise resolves. const comments = use(commentsPromise); return comments.map(comment => <p key={comment.id}>{comment}</p>); } function Page({commentsPromise}) { // When `use` suspends in Comments, // this Suspense boundary will be shown. return ( <Suspense fallback={<div>Loading...</div>}> <Comments commentsPromise={commentsPromise} /> </Suspense> ) }
همونطور که در کد میبینین use پرامیس کامنتها رو تا زمانی که resolve بشن توی حالت Suspend قرار میده که این کار با اتریبیوت fallback در کامپوننت Suspense انجام میشه و هر وقت که resolve شد کامپوننت Comments اجرا میشه.
⚠️ use از پرامیسهای ساخته شده در رندر پشتیبانی نمیکنه و اگر بسازین به ارور زیر بر میخورین :
A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.
برای رفع این ارور استفاده از کتابخونههایی مثل SWR یا React Query میتونه گزینه خوبی باشه.
همچنین از use میتونیم برای خوندن Context ها استفاده کنیم :
import { use } from 'react'; import ThemeContext from './ThemeContext' function Heading({children}) { if (children == null) { return null; } // This would not work with useContext // because of the early return. const theme = use(ThemeContext); return ( <h1 style={{color: theme.color}}> {children} </h1> ); }
استفاده از use به جای useContext به این دلیله که use میتونه با مقادیر Context کار کنه حتی اگر قبل از آن زودتر return بشه. اگر از useContext استفاده میکردیم، نمیتونستیم قبل از فراخوانی useContext از return استفاده کنیم، چون این کار منجر به خطای هوکهای ریاکت میشد.
۶. React DOM Static APIs
در ریاکت ۱۹ دو API به نامهای prerender و prerenderToNodeStream از کتابخونه react-dom/static معرفی شدند که برای تولید سایتهای ایستا (static) اضافه شدن و یه جورایی نسخه بهبود یافته renderToString هستن. اما بریم سراغ اینکه تفاوتش با renderToString در چی هست؟ 🤔
renderToString دادهها رو به محض لود شدن برمیگردوند، اما این API ها منتظر میمونن و زمانی که همه دادهها قبل از تولید HTML لود بشن و اون رو برمیگردونن.
این هوکها اطمینان بیشتری به ما میدن تا دادههای ما کامل لود بشه. چون همونطور که گفتم منتظر میمونن تا همه داده ها دریافت و بعد لود بشن و سپس اون رو به نمایش میذارن.
اما نحوه استفاده ازش چه شکلیه؟❓
import { prerender } from 'react-dom/static'; async function handler(request) { const {prelude} = await prerender(<App />, { bootstrapScripts: ['/main.js'] }); return new Response(prelude, { headers: { 'content-type': 'text/html' }, }); }
همونطور که میبینید prerender کامپوننت </App> رو به HTML استاتیک تبدیل میکنه .
bootstrapScripts، اسکریپت های مورد نیاز برای راهاندازی برنامه رو فراهم میکنه. یعنی فایلی هست که ریاکت توی اون فایل HTML رو لود میکنه و این فرایند به ریاکت اجازه میده تا صفحه استاتیک رو به یک برنامه تعاملی تبدیل کنه.
محتوای فایل main.js معمولا به این شکله :
import React from 'react'; import { hydrateRoot } from 'react-dom/client'; import App from './App'; hydrateRoot(document.getElementById('root'), <App />);
۷. React Server Components
Server Components
React Server Components (RSC) قابلیت جدیدی در ریاکت ۱۹ است که اجازه میده بخشی از کامپوننتها روی سرور اجرا شده و خروجی HTML به مرورگر ارسال بشه. این کامپوننتها میتوانند در زمان ساخت یا هنگام هر درخواست اجرا بشن. مزایای اصلی شامل کاهش حجم کد کلاینت، بهبود عملکرد و کاهش بار مرورگر است:
// Example.server.js export default async function Example() { const data = await fetchDataFromDatabase(); return <div>{data}</div>; }
این کامپوننت در سرور اجرا شده و HTML آماده را به مرورگر ارسال میکنه. میتونین برای اطلاعات بیشتر مراجعه کنین.
Server Actions
Server Actions به شما این امکان رو میده که از کلاینت توابع Async که تعریفشده کردین رو توی سرور را فراخوانی کنین. با استفاده از دستور"use server"، فریمورک بهطور خودکار یک رفرنس برای این توابع ایجاد میکنه و آن را به کامپوننت کلاینت منتقل می کنه.
مثال :
"use server"; export async function saveData(data) { await database.save(data); return { success: true }; } import { saveData } from "./actions.server"; function ClientComponent() { return <button onClick={() => saveData({ id: 1 })}>Save</button>; }
در این مثال، فراخوانی تابع از کلاینت، درخواست اجرا در سرور را ارسال میکنه و نتیجه را برمیگردونه.
۸. بهبودها در ریاکت ۱۹
ref
در ریاکت ۱۹ میتونین ref رو به صورت Prop پاس بدین به کامپوننت دیگه. این ویژگی نیاز به استفاده از forwardRef رو حذف میکنه:
function MyInput({placeholder, ref}) { return <input placeholder={placeholder} ref={ref} /> } //... <MyInput ref={ref} />
⚠️ ref همچنان در کامپوننتهای Class-based نمیتونه بصورت Prop پاس داده بشه.
⚠️ ریاکت گفته در نسخههای آینده قصد داره استفاده از forwardRefرو منسوخ کنه.
Context به عنوان Provider
در ریاکت ۱۹ میتونین کانتکست رو به صورت Provider رندر کنین و بجای استفاده از <Context.Provider> میتونین فقط اسم کانتکست رو بنویسین:
const ThemeContext = createContext(''); function App({children}) { return ( <ThemeContext value="dark"> {children} </ThemeContext> ); }
تابع Cleanup برای ref
توی نسخهٔ جدید ریاکت میتونیم برای ref یک Cleanup Function داشته باشیم:
<input ref={(ref) => { // ref created // NEW: return a cleanup function to reset // the ref when element is removed from DOM. return () => { // ref cleanup }; }} />
هنگامی که کامپوننت حذف میشه (unmount)، تابع Cleanup توسط ریاکت فراخوانی میشه.
⚠️ در نسخههای قبلی وقتی کامپوننت حذف میشد ریاکت به ref مقدار null میداد. اما توی این نسخه ریاکت اگر Cleanup Function داشته باشیم این مرحله انجام نمیشه.
۹. Initial Value در useDeferredValue
در نسخه جدید ریاکت یک قابلیت به نام Initial Value به هوک useDeferredValue اضافه شده که در اون میتونین یک مقدار اولیه رو برای رندر اولیه کامپوننت ست کنین. مقدار نهایی به صورت Async تنظیم میشه:
function Search({deferredValue}) { // On initial render the value is ''. // Then a re-render is scheduled with the deferredValue. const value = useDeferredValue(deferredValue, ''); return ( <Results query={value} /> ); }
هنگام رندر اولیه مقدار deferredValue بازگردونده میشه. در مثال بالا مقدارش رو ' ' ست کرده. بعد از رندر اولیه مقدار deferredValue به صورت Async محاسبه میشه و باعث میشه کامپوننت مجدد رندر بشه (re-render).
۱۰. پشتیبانی از متادیتاها
در ریاکت ۱۹ امکان اضافه کردن متادیتاها فراهم شده. همونطور که میدونین تگهایی مثل link و meta و title باید در تگ head قرار بگیرن. اما در ریاکت این تگها ممکنه در کامپوننتهایی قرار بگیرند که فاصله زیادی با تگ head دارن. در نسخههای قبلی شما باید از کتابخونه ای به نام react-helmet استفاده میکردین. اما در نسخهٔ جدید ریاکت امکان رندر کردن اونها فراهم شده و ریاکت این تگها رو شناسایی میکنه در تگ head قرار میده:
function BlogPost({post}) { return ( <article> <h1>{post.title}</h1> <title>{post.title}</title> <meta name="author" content="Josh" /> <link rel="author" href="https://twitter.com/joshcstory/" /> <meta name="keywords" content={post.keywords} /> <p> Eee equals em-see-squared... </p> </article> ); }
⚠️ با وجود اینکه این ویژگی به صورت بومی در ریاکت وجود داره، ممکنه هنوز بخواین از کتابخانههایی مثل react-helmet برای کاربردهای پیچیدهتر استفاده کنید. این کتابخانهها ویژگیهای بیشتری مانند توانایی تغییر متادیتا بر اساس مسیر جاری را ارائه میدن که در برخی فریمورکها و کتابخانهها مفید هستن.
۱۱. پشتیبانی Stylesheet ها
در ریاکت ۱۹، پشتیبانی داخلی از استایلها فراهم شده که همزمان با رندر همزمان (Concurrent Rendering) در کلاینت و رندر استریم (Streaming Rendering) در سرور کار میکند. ریاکت به طور خودکار ترتیب بارگذاری استایلها را در DOM مدیریت میکند تا مطمئن بشه که استایلهای خارجی قبل از نمایش محتوایی که به آنها وابسته هست، بارگذاری میشن.
مثال:
function ComponentOne() { return ( <Suspense fallback="loading..."> <link rel="stylesheet" href="foo" precedence="default" /> <link rel="stylesheet" href="bar" precedence="high" /> <article class="foo-class bar-class"> {...} </article> </Suspense> ) } function ComponentTwo() { return ( <div> <p>{...}</p> <link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar </div> ) }
در ComponentOne دو استایل foo و bar وجود دارد که اولویتهای مختلف دارن. در ComponentTwo یک استایل جدید baz اضافه میشود که ریاکت به طور خودکار آن را در ترتیب مناسب بین foo و bar وارد میکنه. اگر استایلها به صورت خارجی بارگذاری بشن، ریاکت مطمئن میشه که قبل از نمایش محتوا، استایلها بارگذاری شده باشند.
⚠️ در رندر سمت کلاینت (Client Side Rendering)، ریاکت برای بارگذاری استایلهای جدید صبر میکنه تا مطمئن بشه این استایلها قبل از تکمیل رندر شدن محتوایی که به آنها وابسته است، به طور کامل بارگذاری شدن. همچنین اگر یک کامپوننت در چندین جای اپلیکیشن رندر بشه، ریاکت به صورت هوشمند از وارد کردن مکرر لینک استایل به DOM جلوگیری میکنه که باعث افزایش عملکرد برنامه میشه.
مثال:
function App() { return <> <ComponentOne /> ... <ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM </> }
⚠️ اگر ComponentOne شامل یک لینک استایل باشه، ریاکت فقط یک بار اون رو به DOM اضافه میکنه، حتی اگر این کامپوننت چندین بار در جاهای مختلف اپلیکیشن رندر بشه.
۱۲. پشتیبانی از اسکریپتهای Async
در نسخه جدید پشتیبانی از اسکریپتهای Async بهبود یافته. ریاکت این مسئله رو با اجازه دادن رندر کردن این اسکریپتها در هر جای کامپوننت و بدون نیاز به مدیریت دستی حل کرده که شامل حذف تکرار اسکریپت هاست:
function MyComponent() { return ( <div> <script async={true} src="..." /> Hello World </div> ) } function App() { <html> <body> <MyComponent> ... <MyComponent> // won't lead to duplicate script in the DOM </body> </html> }
همونطور که میبینید با رندر دوباره کامپوننت اون اسکریپت مجددا در HTML بارگذاری نمیشه.
۱۳. پیش بارگذاری منابع
در نسخه جدید قابلیتهای جدیدی برای پیش بارگذاری منابع معرفی شده که به ما این امکان رو میده تا منابع مورد نیاز مرورگر رو سریعتر شناسایی و بارگذاری کنیم. این کار موجب افزایش سرعت عملکرد صفحه به ویژه هنگام بارگذاری اولیه و بروزرسانیهای سمت کلاینت میشه. در اینجا چند تا API برای مدیریت منابع براتون اوردم که از react-dom در دسترس هست.
preinit: برای بارگذاری و اجرای فوری اسکریپتها.preload: برای پیشبارگذاری منابعی مانند فونتها، استایلها و تصاویر.prefetchDNS: برای رزولوشن DNS قبل از درخواست به یک هاست خاص.preconnect: برای برقراری اتصال اولیه با هاستهایی که قرار است در آینده از آنها درخواست بشه.
مثال :
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom' function MyComponent() { preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet prefetchDNS('https://...') // when you may not actually request anything from this host preconnect('https://...') // when you will request something but aren't sure what }
⚠️کد بالا باعث میشه مرورگر منابع رو به ترتیب اولویت برای بهبود بارگذاری مدیریت کنه.
خروجی HTML :
<!-- the above would result in the following DOM/HTML --> <html> <head> <!-- links/scripts are prioritized by their utility to early loading, not call order --> <link rel="prefetch-dns" href="https://..."> <link rel="preconnect" href="https://..."> <link rel="preload" as="font" href="https://.../path/to/font.woff"> <link rel="preload" as="style" href="https://.../path/to/stylesheet.css"> <script async="" src="https://.../path/to/some/script.js"></script> </head> <body> ... </body> </html>
۱۴. گزارش بهتر خطاها
در نسخه جدید گزارشها بهبود یافته و خطاهای تکراری جایگزین خطاهای جدید شدن تا کار توسعهدهندهها راحتتر بشه. مشکلات نسخه قبلی این بود که گاهی اوقات خطاها رو چند بار نمایش میداد:
- خطای اصلی
- یک پیام اضافی در
console.errorست میکرد. - یک خطا برای عدم بازیابی خودکار میداد.
این فرایند باعث میشد برای هرخطای گرفته شده سه گزارش در کنسول ثبت میشد.
اما در نسخه جدید بصورت تک خطی داریم :
Error: hit at Throws at renderWithHooks … The above error occurred in the Throws component: at Throws at ErrorBoundary at App React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. at ErrorBoundary at App
⚠️ سه گزینه جدید برای مدیریت خطا در createRoot و hydrateRoot معرفی شدند.
onCaughtError: زمانی فراخوانی میشود که ریاکت یک خطا را در یک Error Boundary میگیرد.onUncaughtError: زمانی که یک خطا پرتاب شود و توسط هیچ Error Boundary ای گرفته نشود.onRecoverableError: زمانی که یک خطا پرتاب شود و به طور خودکار توسط ریاکت بازیابی شود.
مثال :
import { createRoot } from 'react-dom/client'; const root = createRoot(document.getElementById('root'), { onCaughtError: (error, info) => { console.error('Caught Error:', error, info); }, onUncaughtError: (error) => { console.error('Uncaught Error:', error); }, onRecoverableError: (error) => { console.warn('Recoverable Error:', error); }, });
خب دوستان این بود از ویژگیهای ریاکت ۱۹ که تو این پستِ بهش پرداختیم :)
اگر جایی براتون مبهمه یا سوالی دارین، میتونین در بخش کامنت ها بپرسین و حتما پاسخگو خواهم بود☘️
منبع :
