React context یک ابزار ضروری است که هر توسعهدهنده React باید آن را بداند. در این مقاله قصد داریم تا درمورد این که React context چیست، چگونه باید از آن استفاده کنیم، در چه مواقعی باید آن را به کار ببریم و غیره صحبت کنیم و به همراه مثالهای ساده و گام به گام همه موارد را باهم بررسی کنیم.
React context این امکان را به ما میدهد تا دادهها را در هر کامپوننتی که در برنامه React به آن نیاز داریم، بدون استفاده از props منتقل کرده و مورد استفاده قرار دهیم. به عبارت دیگر، React context باعث میشود تا ما دادهها (state) را در کامپوننتهای خود راحتتر به اشتراک بگذاریم.
هنگامی که در برنامه خود دادههایی را ارسال میکنیم که میتوانند در هر کامپوننتی از برنامه مورد استفاده قرار بگیرند، در این صورت React context بهترین انتخاب برای این کار است.
این نوع دادهها عبارتند از:
برای این کار دادهها باید در React context قرار داده شوند و اغلب نیازی به بهروزرسانی ندارند. چرا؟ زیرا context به عنوان سیستم مدیریت state ساخته نشده است بلکه کاربرد آن تسهیل مصرف دادهها میباشد.
میتوانیم React context را معادل متغیرهای سراسری برای کامپوننتهای خود در نظر بگیریم.
React context کمک میکند تا از مشکل props drilling جلوگیری کنیم. props drilling اصطلاحی است برای توصیف زمانی که props را از طریق کامپوننتهایی که به آن نیاز ندارند چندین سطح به یک کامپوننت تودرتو منتقل میکنیم.
در ادامه یک نمونه از props drilling آورده شده است. در این برنامه، ما به دادههای تم دسترسی داریم و میخواهیم آنها را به عنوان props به تمام کامپوننتهای برنامه خود منتقل کنیم.
همانطور که میبینید childهای مستقیم App
، مانند Header
نیز باید دادههای تم را با استفاده از props منتقل کنند.
export default function App({ theme }) { return ( <> <Header theme={theme} /> <Main theme={theme} /> <Sidebar theme={theme} /> <Footer theme={theme} /> </> ); } function Header({ theme }) { return ( <> <User theme={theme} /> <Login theme={theme} /> <Menu theme={theme} /> </> ); }
مشکل این مثال چیست؟ مشکل این است که ما در حال drilling پراپ theme
از طریق کامپوننتهای متعددی هستیم که فوراً به آن نیاز ندارند.
کامپوننت Header
به theme
دیگری به جز انتقال آن به کامپوننت child خود نیاز ندارد. به عبارت دیگر، بهتر است User
،Login
و Menu
دادههای theme
را مستقیما مورد استفاده قرار دهند.
این مزیت React context است یعنی ما میتوانیم استفاده از props را بهطور کامل دور بزنیم و بنابراین از مشکل props drilling اجتناب کنیم.
context یک API است که از نسخه ۱۶ در React ساخته شده است. این به این معنی است که می توانیم با import کردن React در پروژه خود به طور مستقیم context ایجاد کرده و از آن استفاده کنیم.
برای استفاده از React context چهار مرحله وجود دارد:
createContext
میسازیم.value
، هر مقداری که دوست داریم را در context provider قرار میدهیم.ممکن است مراحل گیجکننده به نظر برسد اما یک موضوع بسیار ساده است.
در ادامه مثال بسیار مهمی را باهم بررسی میکنیم. هدفی که داریم این است که در App
نام خود را با استفاده از context منتقل کنیم و آن را در یک کامپوننت تودرتو یعنی User
بخوانیم.
import React from 'react'; export const UserContext = React.createContext(); export default function App() { return ( <UserContext.Provider value="Reed"> <User /> </UserContext.Provider> ) } function User() { return ( <UserContext.Consumer> {value => <h1>{value}</h1>} {/* prints: Reed */} </UserContext.Consumer> ) }
App
، با استفاده از createContext()
یک context میسازیم و نتیجه را در متغیر UserContext
قرار میدهیم. تقریباً در هر موردی که بخواهیم، همانند کاری که اینجا انجام دادهایم میتوانیم آن را export کنیم، زیرا کامپوننت ما در فایل دیگری قرار خواهد گرفت. باید توجه داشته باشیم وقتی که React.createContext()
را فراخوانی میکنیم، میتوانیم یک مقدار اولیه را به پراپ value
ارسال کنیم.App
از UserContext
و به طور خاص از UserContext.Provider
استفاده میکنیم. context ایجاد شده یک آبجکت با دو ویژگی است: Provider
و Consumer
که هر دو کامپوننت هستند. برای اینکه value
خود را به هر کامپوننت در برنامه منتقل کنیم، آن را داخل کامپوننت Provider
قرار میدهیم (در این قسمت منظور ما کامپوننت User
است).Provider
، مقداری را که میخواهیم به کل درخت کامپوننت منتقل کنیم قرار میدهیم. که اینجا آن را برابر با پراپ value
برای انجام این کار درنظر گرفتهایم. این مورد، همان نام مورد نظر ما است که در اینجا Reed
میباشد.User
یا هر جای دیگری که بخواهیم آنچه را که در context ارائه شده است مورد استفاده قرار دهیم از کامپوننت مصرف کننده UserContext.Consumer
استفاده میکنیم. برای استفاده از مقدار ارسال شده، از چیزی استفاده میکنیم که الگوی رندر props نامیده میشود. این فقط یک تابع است که کامپوننت مصرف کننده به عنوان یک prop به ما میدهد. و در مقدار بازگشتی آن تابع میتوانیم value
را برگردانیم و از آن استفاده کنیم.با نگاهی به مثال بالا، الگوی رندر props برای استفاده از context ممکن است کمی عجیب به نظر برسد. اما راه دیگری برای استفاده از context در React 16.8 با ورود هوکهای React در دسترس قرار گرفت. اکنون میتوانیم از هوکها کمک بگیریم و از context استفاده کنیم.
در این حالت به جای استفاده از رندر props، میتوانیم کل آبجکت متنی را به React.useContext()
منتقل کنیم تا context را در کامپوننت مورد نظر ما مصرف کند.
در ادامه مثال بالا را با استفاده از هوک useContext بررسی کردهایم:
import React from 'react'; export const UserContext = React.createContext(); export default function App() { return ( <UserContext.Provider value="Reed"> <User /> </UserContext.Provider> ) } function User() { const value = React.useContext(UserContext); return <h1>{value}</h1>; }
مزیت هوک useContext این است که کامپوننت را مختصرتر میکند و این امکان را به ما میدهد تا بتوانیم هوکهای سفارشی مورد نظر خود را ایجاد کنیم.
بسته به اینکه کدام الگو را ترجیح میدهیم، میتوانید مستقیماً از کامپوننت مصرف کننده یا از هوک useContext استفاده کنیم.
اشتباهی که بسیاری از توسعهدهندگان مرتکب آن میشوند این است که وقتی مجبور میشوند props را چندین سطح به یک کامپوننت منتقل کنند، به دنبال context هستند.
در ادامه یک برنامه با یک کامپوننت Avatar
تودرتو داریم که به دو پراپ username
و avatarSrc
از کامپوننت App
نیاز دارد.
export default function App({ user }) { const { username, avatarSrc } = user; return ( <main> <Navbar username={username} avatarSrc={avatarSrc} /> </main> ); } function Navbar({ username, avatarSrc }) { return ( <nav> <Avatar username={username} avatarSrc={avatarSrc} /> </nav> ); } function Avatar({ username, avatarSrc }) { return <img src={avatarSrc} alt={username} />; }
در صورت امکان میخواهیم از انتقال چندین props از طریق کامپوننتهایی که به آن نیازی ندارند اجتناب کنیم. اما چگونه میتوانیم این کار را انجام دهیم؟
به جای اینکه به دلیل prop drilling فوراً به context متوسل شویم، بهتر است کامپوننت خود را بسازیم.
از آنجایی که فقط بالاترین کامپوننت یعنی App
باید از کامپوننت Avatar
اطلاع داشته باشد از این رو میتوانیم آن را مستقیماً در App
ایجاد کنیم.
این کار باعث میشود تا بتوانیم به جای دو props، یک پراپ Avatar
را منتقل کنیم.
export default function App({ user }) { const { username, avatarSrc } = user; const avatar = <img src={avatarSrc} alt={username} />; return ( <main> <Navbar avatar={avatar} /> </main> ); } function Navbar({ avatar }) { return <nav>{avatar}</nav>; }
به طور خلاصه یعنی این که نباید سریعا به فکر استفاده از context باشیم، بلکه باید ببینیم آیا میتوانیم کامپوننتهای خود را بهتر سازماندهی کنم تا از prop drilling جلوگیری کنیم.
پاسخ این پرسش میتواند هم بله باشد و هم خیر.
برای بسیاری از مبتدیان ریاکت، Redux راهی برای انتقال آسانتر دادهها بهشمار میآید. دلیل این امر این است که Redux با خود React context ارائه میکند. با این حال اگر state را بهروزرسانی نمیکنیم بلکه صرفاً آن را به درخت کامپوننت خود منتقل میکنیم، به یک کتابخانه مدیریت state سراسری مانند Redux نیاز نداریم.
سوالی که وجود دارد این است که چرا نمیتوانیم مقداری را که React context منتقل میکند بهروزرسانی کنیم؟
در حالی که میتوانیم React context را با یک هوک مانند useReducer ترکیب کنیم و یک کتابخانه مدیریت state موقت بدون کتابخانه third-party ایجاد کنیم، اما این کار معمولاً به دلایل مربوط به کارایی توصیه نمیشود. مشکل این رویکرد در روشی است که React context باعث ایجاد رندر مجدد میشود.
اگر یک آبجکتی را در ارائهدهنده React context خود ارسال کنیم و هر ویژگی که در آن وجود دارد بهروزرسانی شود در این صورت اتفاقی که میافتد این است که هر کامپوننتی که آن context را مصرف کند دوباره رندر میشود.
همنی موضوع ممکن است در برنامههای کوچکتر با مقادیر state کم که اغلب بهروزرسانی نمیشوند (مانند دادههای تم) یک مشکل عملکردی نباشد. اما اگر بهروزرسانیهای state زیادی را در برنامهای با کامپوننتهای زیاد در درخت کامپوننت خود انجام دهیم، یک مشکل بزرگ به حساب میآید.
دیدگاهها:
Amohii
مرداد 27, 1403 در 2:57 ب.ظ
بسیار عالی ممنون از توضیحات شما