۵۰ درصد تخفیف ویژه برای همه دوره‌ها تا پایان هفته

مفهوم Context در React

React context یک ابزار ضروری است که هر توسعه‌دهنده React باید آن را بداند. در این مقاله قصد داریم تا درمورد این که React context چیست، چگونه باید از آن استفاده کنیم، در چه مواقعی باید آن را به کار ببریم و غیره صحبت کنیم و به همراه مثال‌های ساده و گام به گام همه موارد را باهم بررسی کنیم.

React context چیست؟

React context این امکان را به ما می‌دهد تا داده‌ها را در هر کامپوننتی که در برنامه React به آن نیاز داریم، بدون استفاده از props منتقل کرده و مورد استفاده قرار دهیم. به عبارت دیگر، React context باعث می‌شود تا ما داده‌ها (state) را در کامپوننت‌های خود راحت‌تر به اشتراک بگذاریم.

چه زمانی باید از زمینه React استفاده کنیم؟

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

این نوع داده‌ها عبارتند از:

  • اطلاعات تم (مانند حالت تاریک یا روشن)
  • اطلاعات کاربر (کاربر تأیید شده فعلی)
  • اطلاعات Location-specific (مانند زبان کاربر یا منطقه محلی)

برای این کار داده‌ها باید در React context قرار داده شوند و اغلب نیازی به به‌روزرسانی ندارند. چرا؟ زیرا context به عنوان سیستم مدیریت state ساخته نشده است بلکه کاربرد آن تسهیل مصرف داده‌ها می‌باشد.

می‌توانیم React context را معادل متغیرهای سراسری برای کامپوننت‌های خود در نظر بگیریم.

React context چه مشکلاتی را حل می‌کند؟

React context کمک می‌کند تا از مشکل props drilling جلوگیری کنیم. props drilling اصطلاحی است برای توصیف زمانی که props را از طریق کامپوننت‌هایی که به آن نیاز ندارند چندین سطح به یک کامپوننت تودرتو منتقل می‌کنیم.

در ادامه یک نمونه از props drilling آورده شده است. در این برنامه، ما به داده‌های تم دسترسی داریم و می‌خواهیم آن‌ها را به عنوان props به تمام کامپوننت‌های برنامه خود منتقل کنیم.

همانطور که می‌بینید childهای مستقیم

App
App، مانند
Header
Headerنیز باید داده‌های تم را با استفاده از props منتقل کنند.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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} />
</>
);
}
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} /> </> ); }
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
themeاز طریق کامپوننت‌های متعددی هستیم که فوراً به آن نیاز ندارند.

کامپوننت

Header
Headerبه
theme
themeدیگری به جز انتقال آن به کامپوننت child خود نیاز ندارد. به عبارت دیگر، بهتر است
User
User،
Login
Loginو
Menu
Menuداده‌های
theme
themeرا مستقیما مورد استفاده قرار دهند.

این مزیت React context است یعنی ما می‌توانیم استفاده از props را به‌طور کامل دور بزنیم و بنابراین از مشکل props drilling اجتناب کنیم.

نحوه استفاده از React context

context یک API است که از نسخه ۱۶ در React ساخته شده است. این به این معنی است که می توانیم با import کردن React در پروژه خود به طور مستقیم context ایجاد کرده و از آن استفاده کنیم.

برای استفاده از React context چهار مرحله وجود دارد:

  1. context با استفاده از متد
    createContext
    createContextمی‌سازیم.
  2. context ایجاد شده را در نظر گرفته و درخت کامپوننت را داخل provider آن قرار می‌دهیم.
  3. با استفاده از پراپ
    value
    value، هر مقداری که دوست داریم را در context provider قرار می‌دهیم.
  4. با استفاده از context consumer، آن مقدار را در هر کامپوننتی که بخواهیم می‌توانیم مورد استفاده قرار دهیم.

ممکن است مراحل گیج‌کننده به نظر برسد اما یک موضوع بسیار ساده است.

در ادامه مثال بسیار مهمی را باهم بررسی می‌کنیم. هدفی که داریم این است که در

App
Appنام خود را با استفاده از context منتقل کنیم و آن را در یک کامپوننت تودرتو یعنی
User
Userبخوانیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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>
)
}
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> ) }
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>
  )
}

تحلیل گام به گام مثال بالا:

  1. در بالای کامپوننت
    App
    App، با استفاده از
    createContext()
    createContext()یک context می‌سازیم و نتیجه را در متغیر
    UserContext
    UserContextقرار می‌دهیم. تقریباً در هر موردی که بخواهیم، همانند کاری که اینجا انجام داده‌ایم می‌توانیم آن را export کنیم، زیرا کامپوننت ما در فایل دیگری قرار خواهد گرفت. باید توجه داشته باشیم وقتی که
    React.createContext()
    React.createContext()را فراخوانی می‌کنیم، می‌توانیم یک مقدار اولیه را به پراپ
    value
    valueارسال کنیم.
  2. در کامپوننت
    App
    App از
    UserContext
    UserContextو به طور خاص از
    UserContext.Provider
    UserContext.Providerاستفاده می‌کنیم. context ایجاد شده یک آبجکت با دو ویژگی است:
    Provider
    Provider و
    Consumer
    Consumerکه هر دو کامپوننت هستند. برای اینکه
    value
    valueخود را به هر کامپوننت در برنامه منتقل کنیم، آن را داخل کامپوننت
    Provider
    Providerقرار می‌دهیم (در این قسمت منظور ما کامپوننت
    User
    Userاست).
  3. در
    Provider
    Provider، مقداری را که می‌خواهیم به کل درخت کامپوننت منتقل کنیم قرار می‌دهیم. که اینجا آن را برابر با پراپ
    value
    valueبرای انجام این کار درنظر گرفته‌ایم. این مورد، همان نام مورد نظر ما است که در اینجا
    Reed
    Reedمی‌باشد.
  4. برای این که بتوانیم در کامپوننت
    User
    Userیا هر جای دیگری که بخواهیم آنچه را که در context ارائه شده است مورد استفاده قرار دهیم از کامپوننت مصرف کننده
    UserContext.Consumer
    UserContext.Consumerاستفاده می‌کنیم. برای استفاده از مقدار ارسال شده، از چیزی استفاده می‌کنیم که الگوی رندر props نامیده می‌شود. این فقط یک تابع است که کامپوننت مصرف کننده به عنوان یک prop به ما می‌دهد. و در مقدار بازگشتی آن تابع می‌توانیم
    value
    valueرا برگردانیم و از آن استفاده کنیم.

منظور از هوک useContext چیست؟

با نگاهی به مثال بالا، الگوی رندر props برای استفاده از context ممکن است کمی عجیب به نظر برسد. اما راه دیگری برای استفاده از context در React 16.8 با ورود هوک‌های React در دسترس قرار گرفت. اکنون می‌توانیم از هوک‌ها کمک بگیریم و از context استفاده کنیم.

در این حالت به جای استفاده از رندر props، می‌توانیم کل آبجکت متنی را به

React.useContext()
React.useContext()منتقل کنیم تا context را در کامپوننت مورد نظر ما مصرف کند.

در ادامه مثال بالا را با استفاده از هوک useContext بررسی کرده‌ایم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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>;
}
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>; }
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 استفاده کنیم.

ممکن است نیازی به context نداشته باشیم

اشتباهی که بسیاری از توسعه‌دهندگان مرتکب آن می‌شوند این است که وقتی مجبور می‌شوند props را چندین سطح به یک کامپوننت منتقل کنند، به دنبال context هستند.

در ادامه یک برنامه با یک کامپوننت

Avatar
Avatarتودرتو داریم که به دو پراپ
username
usernameو
avatarSrc
avatarSrcاز کامپوننت
App
Appنیاز دارد.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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} />;
}
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} />; }
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
Appباید از کامپوننت
Avatar
Avatarاطلاع داشته باشد از این رو می‌توانیم آن را مستقیماً در
App
Appایجاد کنیم.

این کار باعث می‌شود تا بتوانیم به جای دو props، یک پراپ

Avatar
Avatarرا منتقل کنیم.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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>;
}
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>; }
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 جلوگیری کنیم.

آیا React context می‌تواند جایگزین Redux شود؟

پاسخ این پرسش می‌تواند هم بله باشد و هم خیر.

برای بسیاری از مبتدیان ری‌اکت، Redux راهی برای انتقال آسان‌تر داده‌ها به‌شمار می‌آید. دلیل این امر این است که Redux با خود React context ارائه می‌کند. با این حال اگر state را به‌روزرسانی نمی‌کنیم بلکه صرفاً آن را به درخت کامپوننت خود منتقل می‌کنیم، به یک کتابخانه مدیریت state سراسری مانند Redux نیاز نداریم.

اخطارهایی درمورد React context

سوالی که وجود دارد این است که چرا نمی‌توانیم مقداری را که React context منتقل می‌کند به‌روزرسانی کنیم؟

در حالی که می‌توانیم React context را با یک هوک مانند useReducer ترکیب کنیم و یک کتابخانه مدیریت state موقت بدون کتابخانه third-party ایجاد کنیم، اما این کار معمولاً به دلایل مربوط به کارایی توصیه نمی‌شود. مشکل این رویکرد در روشی است که React context باعث ایجاد رندر مجدد می‌شود.

اگر یک آبجکتی را در ارائه‌دهنده React context خود ارسال کنیم و هر ویژگی که در آن وجود دارد به‌روزرسانی شود در این صورت اتفاقی که می‌افتد این است که هر کامپوننتی که آن context را مصرف کند دوباره رندر می‌شود.

همنی موضوع ممکن است در برنامه‌های کوچک‌تر با مقادیر state کم که اغلب به‌روزرسانی نمی‌شوند (مانند داده‌های تم) یک مشکل عملکردی نباشد. اما اگر به‌روزرسانی‌های state زیادی را در برنامه‌ای با کامپوننت‌های زیاد در درخت کامپوننت خود انجام دهیم، یک مشکل بزرگ به حساب می‌آید.

دیدگاه‌ها:

Amohii

مرداد 27, 1403  در  2:57 ب.ظ

بسیار عالی ممنون از توضیحات شما

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