مفهوم 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، مانند 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 اجتناب کنیم.

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

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

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

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

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

در ادامه مثال بسیار مهمی را باهم بررسی می‌کنیم. هدفی که داریم این است که در 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>
  )
}

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

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

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

با نگاهی به مثال بالا، الگوی رندر 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 استفاده کنیم.

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

اشتباهی که بسیاری از توسعه‌دهندگان مرتکب آن می‌شوند این است که وقتی مجبور می‌شوند 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 جلوگیری کنیم.

آیا 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 زیادی را در برنامه‌ای با کامپوننت‌های زیاد در درخت کامپوننت خود انجام دهیم، یک مشکل بزرگ به حساب می‌آید.

دیدگاه‌ها:

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