در حالت عادی، دادهها در ریآکت به صورت خطی و از طریق پروپها (props) از کامپوننت پدر به فرزند منتقل میشوند. اما فرض کنید میخواهید دیتایی مثل تم شب/روز (Theme)، اطلاعات کاربر لاگین شده یا زبان برنامه را به کامپوننتی در دهمین لایه از زیردرخت UI برسانید.
در این شرایط، پاس دادن لایه به لایه پروپها کاری بشدت فرسایشی و غیراصولی است. ریآکت برای حل این چالش، قابلیتی به نام Context را ارائه داده است که مانند یک «تلهپورت داده» عمل میکند.
پدیده کلافهکننده Prop Drilling چیست؟
چطور Context را جایگزین پروپهای تکراری کنیم؟
کاربردهای رایج و جایگزینهای هوشمندانه Context چیست؟
وقتی مجبور میشوید یک پروپ را صرفاً برای رساندن به دست یک فرزندِ عمیق، از میان دهها کامپوننت واسط (که خودشان هیچ نیازی به آن داده ندارند) عبور دهید، با پدیده Prop Drilling مواجه شدهاید.
همانطور که در نمودار بالا مشخص است، به کمک Context میتوان کامپوننتهای واسط را کاملاً نادیده گرفت و داده را مستقیماً به مقصد رساند.
بیایید یک سیستم مدیریت تم (تیره/روشن) بسازیم. میخواهیم بدون پاس دادن پروپ، کاری کنیم که تمام دکمهها و باکسهای متنی درون برنامه از تمِ کامپوننت ریشه تبعیت کنند.
ابتدا یک فایل مجزا (مثلاً ThemeContext.js) میسازیم و ریشه بافت داده را با یک مقدار پیشفرض (اینجا 'light') ایجاد میکنیم:
// ThemeContext.js
import { createContext } from 'react';
// مقدار درون createContext، دیتای پیشفرض سیستم است
export const ThemeContext = createContext('light');
حالا در بالاترین کامپوننت (مثلاً App)، فرزندان را درون کامپوننتِ Context متولد شده قرار میدهیم و مقدار زنده را به آن تزریق میکنیم:
// App.js
import { useState } from 'react';
import { ThemeContext } from './ThemeContext.js';
import Card from './Card.js';
export default function App() {
const [theme, setTheme] = useState('dark');
return (
// با این کار، کل زیردرخت به تم دسترسی خواهند داشت
<ThemeContext value={theme}>
<div className="app-container">
<h1>داشبورد مدیریتی</h1>
<Card />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
تغییر تم
</button>
</div>
</ThemeContext>
);
}
حالا کامپوننت Button را فرض کنید که در اعماق کامپوننت Card قرار دارد. این کامپوننت بدون اینکه مزاحم Card شود، مستقیماً به لطف هوک useContext تم را میخواند:
// Button.js
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext.js';
export default function Button() {
// ریآکت میرود و نزدیکترین ریشه ThemeContext بالای سر این کامپوننت را پیدا میکند
const theme = useContext(ThemeContext);
return (
<button className={`btn btn-${theme}`}>
ذخیره تغییرات (تم فعلی: {theme})
</button>
);
}
یکی از جذابیتهای Context این است که کامپوننتهای فرزند، همیشه مقدارشان را از نزدیکترین Provider بالای سر خود دریافت میکنند. این رفتار دقیقاً شبیه به ارثبری ویژگیها در CSS است.
تصور کنید کل برنامه شما تم light دارد، اما میخواهید یک بخش خاص از سایت (مثلاً سایدبار جانبی) کاملاً تیره (dark) باشد. کافی است آن بخش را دوباره در یک Provider جدید با مقدار متفاوت محصور کنید:
<ThemeContext value="light">
<Header /> {/* تم روشن میگیرد */}
<ThemeContext value="dark">
<Sidebar /> {/* تمام کامپوننتهای داخل سایدبار، تم تیره میگیرند! */}
</ThemeContext>
</ThemeContext>
Context ابزار بسیار وسوسهکنندهای است، اما استفاده بیش از حد از آن ساختار کد شما را مبهم و وابستگی کامپوننتها را شدید میکند (تستپذیری سخت میشود). پیش از استفاده، این دو جایگزین را ارزیابی کنید:
با همان پروپها شروع کنید: اگر کامپوننتها عمیق نیستند، عبور دادن چند پروپ عیب ندارد؛ چرا که مسیر جریان دادهها (Data Flow) در برنامه کاملاً شفاف و مشخص باقی میماند.
کامپوننت را استخراج کرده و از پروپ children استفاده کنید: گاهی کدهای واسط صرفاً بستر نمایش هستند. به جای اینکه دیتا را به پدرِ واسط بدهید تا به فرزند بسپارد، فرزند را در لایه بالاتر رندر کرده و به عنوان تکه JSX پاس دهید:
❌ ساختار شلوغ: <Layout user={user} /> که داخل لایوت رندر میشود: <Sidebar user={user} />
ساختار تمیز: <Layout><Sidebar user={user} /></Layout> (با این کار لایوت اصلاً نیازی به دانستن پروپ user ندارد).
تغییر پوسته برنامه (Theming): مثل حالت دارکمد و لایتمد.
اطلاعات حساب کاربری فعال (Current Account): برای اینکه تمام اجزای کاربری بدانند چه کسی لاگین کرده است.
مسیریابی (Routing): کتابخانههای بزرگ مثل React Router در پشت صحنه از سیستم Context برای فهمیدن آدرس فعلی مرورگر استفاده میکنند.
ترکیب با ردیوسر (Global State): در پروژههای متوسط و بزرگ، ترکیب useReducer و Context یک سیستم مدیریت استیت سراسری بینظیر ایجاد میکند.
ابزار Context به کامپوننتها اجازه میدهد بدون نیاز به عبور سنتی و پلهپله پروپها، دادهها را به اعماق درخت UI بفرستند.
فرآیند پیادهسازی: ایجاد با متد createContext، خواندن با هوک useContext و تزریق مقدار با تگ <MyContext value={...}>.
کامپوننتها هوشمندانه خود را با محیط پیرامون وفق میدهند و دیتای خود را از نزدیکترین پدرِ ارائهدهنده دریافت میکنند.
پیش از پناه بردن به Context، حتماً تکنیک ترکیب کامپوننتها (Component Composition) و انتقال کامپوننت از طریق children را بررسی کنید.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn