مقدمه
در برنامههای React، انتقال داده بین کامپوننتها یکی از رایجترین چالشهایی است که توسعهدهندگان با آن روبهرو میشوند. زمانی که چندین کامپوننت به یک داده مشترک نیاز داشته باشند، ارسال مداوم اطلاعات از طریق Props (که به آن Prop Drilling گفته میشود) باعث پیچیده شدن ساختار پروژه و کاهش خوانایی کد خواهد شد.
برای حل این مشکل، React قابلیتی به نام Context API ارائه کرده است. Context این امکان را فراهم میکند که دادههای مشترک مانند اطلاعات کاربر، تنظیمات برنامه، زبان، تم (Dark/Light Mode) یا سایر Stateهای سراسری را بدون نیاز به ارسال Props در اختیار تمامی کامپوننتهای موردنیاز قرار دهید.
در این آموزش یاد میگیرید چگونه با استفاده از Context، وضعیت (State) را بین چند کامپوننت React به اشتراک بگذارید و پروژهای با ساختار تمیزتر و قابل نگهداریتر ایجاد کنید.
پیشنیازها
برای دنبال کردن این آموزش بهتر است موارد زیر را در اختیار داشته باشید:
- آشنایی اولیه با JavaScript و ES6
- آشنایی با مفاهیم پایه React مانند Component و Props
- نصب Node.js نسخه 20 یا جدیدتر
- نصب npm یا Yarn
- یک ویرایشگر کد مانند Visual Studio Code
در صورتی که از سرورهای پارمین کلود (Parmin Cloud) استفاده میکنید، برای نصب بستههای Node.js میتوانید از میرور npm پارمین کلود استفاده کنید:
npm config set registry https://npm.parmincloud.ir
سپس از درستی تنظیمات مطمئن شوید:
npm config get registry
خروجی باید به شکل زیر باشد:
https://npm.parmincloud.ir
مرحله ۱ — ایجاد پروژه React
ابتدا یک پروژه React جدید ایجاد کنید.
اگر از Vite استفاده میکنید:
npm create vite@latest react-context-demo
وارد پوشه پروژه شوید:
cd react-context-demo
سپس وابستگیها را نصب کنید:
npm install
در نهایت پروژه را اجرا کنید:
npm run dev
اگر همه چیز به درستی انجام شده باشد، پروژه روی آدرسی مشابه زیر اجرا میشود:
http://localhost:5173
ساختار اولیه پروژه
برای این آموزش ساختار سادهای مشابه زیر خواهیم داشت:
src/
│
├── App.jsx
├── main.jsx
│
├── components/
│ ├── Header.jsx
│ ├── Profile.jsx
│ └── Dashboard.jsx
│
└── context/
└── UserContext.jsx
در این پروژه قصد داریم اطلاعات کاربر را تنها یکبار ذخیره کنیم و سپس در چند کامپوننت مختلف از همان اطلاعات استفاده کنیم؛ بدون اینکه مجبور باشیم آن را از والد به فرزند ارسال کنیم.
مشکل ارسال مداوم Props
فرض کنید اطلاعات کاربر در فایل App.jsx قرار دارد:
const user = {
name: "Ali",
role: "Administrator"
};
اگر بخواهید این اطلاعات را در کامپوننت Profile نمایش دهید اما بین این دو چند کامپوننت دیگر وجود داشته باشد، مجبور خواهید شد داده را به این شکل منتقل کنید:
<App>
↓
<Dashboard user={user}>
↓
<Header user={user}>
↓
<Profile user={user}>
در این حالت، کامپوننتهای Dashboard و Header عملاً هیچ استفادهای از متغیر user ندارند و فقط آن را به کامپوننت بعدی ارسال میکنند.
به این وضعیت Prop Drilling گفته میشود.
هرچه پروژه بزرگتر شود، مدیریت این Propsها دشوارتر خواهد شد و تغییر ساختار کامپوننتها نیز پیچیدگی بیشتری پیدا میکند.
Context چگونه این مشکل را حل میکند؟
Context یک فضای مشترک برای نگهداری داده ایجاد میکند.
به جای این ساختار:
App
│
▼
Dashboard
│
▼
Header
│
▼
Profile
اطلاعات مستقیماً در اختیار تمام کامپوننتها قرار میگیرد:
User Context
/ | \
/ | \
Header Profile Dashboard
در نتیجه هر کامپوننتی که به اطلاعات کاربر نیاز داشته باشد، مستقیماً آن را از Context دریافت میکند و دیگر نیازی به ارسال Props از چندین لایه مختلف وجود نخواهد داشت.
مرحله ۲ — ایجاد Context و Provider در React
در این مرحله یک Context ایجاد میکنیم تا اطلاعات کاربر را به تمام کامپوننتهای پروژه منتقل کنیم. سپس با استفاده از Provider این اطلاعات را در اختیار سایر بخشهای برنامه قرار میدهیم.
Context چیست؟
Context یک فضای مشترک برای نگهداری داده در React است.
بهجای اینکه اطلاعات را از طریق Props بین چندین کامپوننت جابهجا کنید، Context داده را یکبار ذخیره میکند و هر کامپوننتی که نیاز داشته باشد، مستقیماً به آن دسترسی خواهد داشت.
هر Context معمولاً از دو بخش تشکیل میشود:
- Context Object
- Provider
Context داده را تعریف میکند و Provider آن داده را در اختیار سایر کامپوننتها قرار میدهد.
ایجاد پوشه Context
ابتدا داخل پوشه src یک پوشه جدید به نام context ایجاد کنید.
سپس فایل زیر را بسازید:
src/context/UserContext.jsx
ایجاد UserContext
فایل UserContext.jsx را باز کرده و کد زیر را وارد کنید:
import { createContext } from "react";
const UserContext = createContext();
export default UserContext;
در اینجا:
createContext()
یک Context جدید ایجاد میکند که در حال حاضر مقدار اولیهای ندارد.
ایجاد Provider
اکنون Provider را نیز به همین فایل اضافه میکنیم.
کد فایل را به شکل زیر تغییر دهید:
import { createContext, useState } from "react";
const UserContext = createContext();
export function UserProvider({ children }) {
const [user, setUser] = useState({
name: "Ali",
role: "Administrator",
email: "ali@example.com"
});
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
}
export default UserContext;
بررسی کد
ابتدا State مربوط به کاربر ایجاد شده است:
const [user, setUser] = useState({
name: "Ali",
role: "Administrator",
email: "ali@example.com"
});
در این مثال اطلاعات کاربر شامل نام، نقش و ایمیل است.
سپس این اطلاعات داخل Provider قرار گرفتهاند:
<UserContext.Provider value={{ user, setUser }}>
ویژگی value مشخص میکند چه دادههایی در اختیار سایر کامپوننتها قرار بگیرد.
در اینجا هم خود اطلاعات کاربر ارسال شده است:
user
و هم تابع تغییر آن:
setUser
بنابراین هر کامپوننتی میتواند:
- اطلاعات کاربر را بخواند.
- اطلاعات کاربر را ویرایش کند.
children چیست؟
ممکن است برایتان سؤال باشد که این قسمت چه کاری انجام میدهد:
{children}
در React، هر چیزی که داخل یک کامپوننت قرار میگیرد، بهعنوان children در نظر گرفته میشود.
برای مثال:
<UserProvider>
<App />
</UserProvider>
در اینجا:
<App />
همان children است.
Provider تمام این کامپوننتها را در بر میگیرد و باعث میشود همه آنها به Context دسترسی داشته باشند.
قرار دادن Provider در ریشه پروژه
اکنون فایل main.jsx را باز کنید.
اگر پروژه را با Vite ساخته باشید، فایل تقریباً به شکل زیر است:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")).render(
<App />
);
آن را به شکل زیر تغییر دهید:
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { UserProvider } from "./context/UserContext";
ReactDOM.createRoot(document.getElementById("root")).render(
<UserProvider>
<App />
</UserProvider>
);
چرا Provider را در main.jsx قرار دادیم؟
زیرا main.jsx نقطه شروع برنامه React است.
وقتی Provider را در این بخش قرار میدهید، تمام کامپوننتهای پروژه به Context دسترسی خواهند داشت.
ساختار برنامه اکنون به شکل زیر خواهد بود:
UserProvider
│
▼
App
├── Dashboard
├── Header
└── Profile
هر سه کامپوننت میتوانند مستقیماً از اطلاعات کاربر استفاده کنند، بدون اینکه Props بین آنها جابهجا شود.
مرحله ۳ — استفاده از Context در کامپوننتهای React
اکنون که Context و Provider را ایجاد کردهاید، نوبت آن است که اطلاعات ذخیرهشده را در سایر کامپوننتهای پروژه استفاده کنید.
برای این کار از هوک useContext استفاده میکنیم.
هوک useContext چیست؟
هوک useContext سادهترین راه برای دسترسی به دادههای موجود در Context است.
به جای ارسال Props از چندین کامپوننت مختلف، تنها کافی است Context موردنظر را فراخوانی کنید.
ساختار کلی آن به شکل زیر است:
const value = useContext(ContextName);
در اینجا:
ContextNameهمان Context ایجادشده باcreateContextاست.- مقدار بازگشتی همان دادهای است که در ویژگی
valueمربوط به Provider قرار دادهاید.
ایجاد کامپوننت Profile
داخل پوشه components فایل جدیدی با نام Profile.jsx ایجاد کنید.
src/components/Profile.jsx
سپس کد زیر را در آن قرار دهید:
import { useContext } from "react";
import UserContext from "../context/UserContext";
function Profile() {
const { user } = useContext(UserContext);
return (
<div>
<h2>اطلاعات کاربر</h2>
<p>نام: {user.name}</p>
<p>سمت: {user.role}</p>
<p>ایمیل: {user.email}</p>
</div>
);
}
export default Profile;
بررسی کد
در ابتدای فایل، هوک موردنیاز را وارد کردهایم:
import { useContext } from "react";
سپس Context پروژه را وارد میکنیم:
import UserContext from "../context/UserContext";
و با استفاده از useContext به اطلاعات دسترسی پیدا میکنیم:
const { user } = useContext(UserContext);
اکنون شیء user دقیقاً همان مقداری است که داخل Provider تعریف کرده بودیم.
نمایش کامپوننت در App
فایل App.jsx را باز کنید.
محتوای آن را به شکل زیر تغییر دهید:
import Profile from "./components/Profile";
function App() {
return (
<div>
<h1>آموزش React Context</h1>
<Profile />
</div>
);
}
export default App;
اجرای پروژه
اگر پروژه را اجرا کنید، خروجی مشابه زیر خواهد بود:
آموزش React Context
اطلاعات کاربر
نام: Ali
سمت: Administrator
ایمیل: ali@example.com
همانطور که مشاهده میکنید، کامپوننت Profile بدون دریافت هیچ Propای به اطلاعات کاربر دسترسی پیدا کرده است.
استفاده از Context در چند کامپوننت
یکی از مهمترین مزیتهای Context این است که چندین کامپوننت میتوانند همزمان از یک داده مشترک استفاده کنند.
برای مثال، کامپوننت Header را ایجاد کنید.
src/components/Header.jsx
import { useContext } from "react";
import UserContext from "../context/UserContext";
function Header() {
const { user } = useContext(UserContext);
return (
<header>
<h3>خوش آمدید {user.name}</h3>
</header>
);
}
export default Header;
اکنون فایل App.jsx را به شکل زیر تغییر دهید:
import Header from "./components/Header";
import Profile from "./components/Profile";
function App() {
return (
<>
<Header />
<Profile />
</>
);
}
export default App;
نتیجه
اکنون هر دو کامپوننت:
HeaderProfile
از یک Context مشترک استفاده میکنند.
نکته مهم اینجاست که هیچ دادهای بین آنها از طریق Props ارسال نشده است.
ساختار پروژه اکنون به این شکل است:
UserProvider
│
▼
App
/ \
▼ ▼
Header Profile
\ /
▼ ▼
UserContext
هر تعداد کامپوننت که بخواهید میتوانند مستقیماً از Context اطلاعات دریافت کنند و دیگر نیازی به ارسال داده از کامپوننت والد به فرزند وجود ندارد.
مرحله ۴ — بهروزرسانی State در Context
تا اینجا توانستید اطلاعات موجود در Context را در چند کامپوننت مختلف نمایش دهید. اما Context فقط برای خواندن اطلاعات نیست؛ شما میتوانید دادههای ذخیرهشده در آن را نیز تغییر دهید.
در این مرحله یاد میگیرید چگونه State موجود در Context را از داخل یک کامپوننت بهروزرسانی کنید و مشاهده کنید که تمام کامپوننتهای وابسته به آن، بهصورت خودکار دوباره رندر میشوند.
چرا State را داخل Context قرار دادیم؟
در فایل UserContext.jsx این کد را نوشتیم:
const [user, setUser] = useState({
name: "Ali",
role: "Administrator",
email: "ali@example.com"
});
سپس هر دو مقدار را داخل Provider قرار دادیم:
<UserContext.Provider value={{ user, setUser }}>
اگر فقط user را ارسال میکردیم، سایر کامپوننتها فقط میتوانستند اطلاعات را مشاهده کنند.
اما با ارسال setUser، تمام کامپوننتها قادر خواهند بود اطلاعات کاربر را نیز تغییر دهند.
ایجاد کامپوننت UpdateUser
داخل پوشه components فایل جدیدی ایجاد کنید:
src/components/UpdateUser.jsx
سپس کد زیر را در آن قرار دهید:
import { useContext } from "react";
import UserContext from "../context/UserContext";
function UpdateUser() {
const { user, setUser } = useContext(UserContext);
function changeUser() {
setUser({
...user,
name: "Parmin"
});
}
return (
<button onClick={changeUser}>
تغییر نام کاربر
</button>
);
}
export default UpdateUser;
بررسی کد
ابتدا اطلاعات Context را دریافت کردهایم:
const { user, setUser } = useContext(UserContext);
این بار علاوه بر user، تابع setUser را نیز دریافت کردهایم.
سپس تابعی برای تغییر اطلاعات ساختهایم:
function changeUser() {
در این تابع از setUser استفاده میکنیم:
setUser({
...user,
name: "Parmin"
});
عملگر Spread چه کاری انجام میدهد؟
در این قسمت:
...user
تمام ویژگیهای شیء قبلی کپی میشوند.
اگر اطلاعات اولیه به شکل زیر باشد:
{
name: "Ali",
role: "Administrator",
email: "ali@example.com"
}
بعد از اجرای کد بالا نتیجه به شکل زیر خواهد بود:
{
name: "Parmin",
role: "Administrator",
email: "ali@example.com"
}
تنها مقدار name تغییر میکند و سایر اطلاعات بدون تغییر باقی میمانند.
اضافه کردن کامپوننت به App
اکنون فایل App.jsx را ویرایش کنید:
import Header from "./components/Header";
import Profile from "./components/Profile";
import UpdateUser from "./components/UpdateUser";
function App() {
return (
<>
<Header />
<Profile />
<UpdateUser />
</>
);
}
export default App;
اجرای برنامه
در اولین اجرا خروجی به شکل زیر خواهد بود:
خوش آمدید Ali
اطلاعات کاربر
نام: Ali
سمت: Administrator
ایمیل: ali@example.com
[تغییر نام کاربر]
پس از کلیک روی دکمه:
خوش آمدید Parmin
اطلاعات کاربر
نام: Parmin
سمت: Administrator
ایمیل: ali@example.com
مشاهده میکنید که هم کامپوننت Header و هم Profile بهصورت خودکار بهروزرسانی شدهاند، بدون اینکه هیچ ارتباط مستقیمی با کامپوننت UpdateUser داشته باشند.
چرا تمام کامپوننتها دوباره رندر شدند؟
زمانی که مقدار Context تغییر میکند، React تمام کامپوننتهایی را که از آن Context استفاده میکنند دوباره رندر میکند.
در این مثال:
UserProvider
│
▼
UserContext
/ | \
▼ ▼ ▼
Header Profile UpdateUser
هر سه کامپوننت به Context متصل هستند.
بنابراین با تغییر State:
Headerدوباره رندر میشود.Profileدوباره رندر میشود.UpdateUserنیز دوباره رندر خواهد شد.
این فرآیند کاملاً خودکار انجام میشود و نیازی به مدیریت دستی آن نیست.
مزیت استفاده از Context در این مثال
اگر از Props استفاده میکردید، برای تغییر نام کاربر باید تابع setUser را از کامپوننت والد به تمام کامپوننتهای میانی ارسال میکردید.
اما با Context:
- هر کامپوننت میتواند اطلاعات را بخواند.
- هر کامپوننت میتواند اطلاعات را تغییر دهد.
- تمام بخشهای برنامه بهصورت خودکار با داده جدید همگام میشوند.
مرحله ۵ — بهترین روشهای استفاده از Context و زمان مناسب برای استفاده از آن
تا اینجا یاد گرفتید چگونه با استفاده از Context اطلاعات را بین چندین کامپوننت به اشتراک بگذارید و آنها را بهروزرسانی کنید. اما سؤال مهم این است که آیا باید تمام Stateهای پروژه را داخل Context قرار دهیم؟
پاسخ کوتاه خیر است.
Context ابزار بسیار قدرتمندی است، اما استفاده نادرست از آن میتواند باعث کاهش کارایی و پیچیده شدن پروژه شود.
در این مرحله با بهترین شیوههای استفاده از Context آشنا میشوید.
چه زمانی از Context استفاده کنیم؟
Context زمانی بهترین انتخاب است که چندین کامپوننت به یک داده مشترک نیاز داشته باشند.
برای مثال:
- اطلاعات کاربر واردشده
- وضعیت ورود یا خروج از حساب کاربری
- زبان سایت
- حالت روشن یا تاریک (Dark Mode)
- تنظیمات کلی برنامه
- اطلاعات سبد خرید در فروشگاه اینترنتی
برای نمونه، اگر بخواهید تم سایت را در همه صفحات تغییر دهید، Context انتخاب بسیار مناسبی است.
const ThemeContext = createContext();
سپس تمام کامپوننتها میتوانند مقدار Theme را دریافت کنند.
چه زمانی از Context استفاده نکنیم؟
فرض کنید یک فرم ثبتنام دارید.
مقدار فیلدهای فرم فقط در همان صفحه استفاده میشود.
در این حالت نیازی نیست اطلاعات فرم را داخل Context قرار دهید.
بهتر است از State معمولی استفاده کنید:
const [email, setEmail] = useState("");
اگر داده فقط در یک یا دو کامپوننت استفاده میشود، استفاده از Context فقط پروژه را پیچیدهتر میکند.
از ایجاد Contextهای بسیار بزرگ خودداری کنید
گاهی توسعهدهندگان تمام اطلاعات برنامه را داخل یک Context قرار میدهند.
مثلاً:
const AppContext = createContext({
user,
products,
orders,
cart,
notifications,
theme,
language,
settings,
...
});
این کار توصیه نمیشود.
زیرا با تغییر یکی از این مقادیر، تعداد زیادی از کامپوننتها دوباره رندر خواهند شد.
بهتر است Contextها را بر اساس وظیفه تقسیم کنید.
برای مثال:
UserContext
ThemeContext
LanguageContext
CartContext
NotificationContext
این ساختار هم خواناتر است و هم عملکرد بهتری دارد.
از قرار دادن دادههای موقت در Context خودداری کنید
فرض کنید یک پنجره (Modal) فقط داخل یک صفحه استفاده میشود.
در این شرایط:
const [open, setOpen] = useState(false);
کاملاً کافی است.
قرار دادن چنین دادههایی در Context هیچ مزیتی ندارد.
Context جایگزین همه ابزارهای مدیریت State نیست
برای پروژههای کوچک و متوسط، Context معمولاً کاملاً کافی است.
اما اگر پروژه بسیار بزرگ باشد و Stateهای زیادی داشته باشید، ابزارهای تخصصیتری گزینه مناسبتری هستند.
از جمله:
- Redux Toolkit
- Zustand
- Jotai
- Recoil
این کتابخانهها امکانات بیشتری مانند مدیریت حرفهای State، بهینهسازی رندرها، DevTools و معماری پیشرفتهتر را در اختیار شما قرار میدهند.
استفاده از Custom Hook
یکی از بهترین روشها این است که بهجای فراخوانی مستقیم useContext در همه فایلها، یک Hook اختصاصی ایجاد کنید.
برای مثال:
import { useContext } from "react";
import UserContext from "./UserContext";
export function useUser() {
return useContext(UserContext);
}
حالا در هر کامپوننت کافی است بنویسید:
const { user } = useUser();
این روش باعث میشود کدها تمیزتر و نگهداری آنها سادهتر شود.
جمعبندی
در این آموزش با React Context API آشنا شدید و یاد گرفتید چگونه بدون نیاز به ارسال مداوم Props، دادههای مشترک را بین کامپوننتهای مختلف به اشتراک بگذارید.
در طول این آموزش:
- با مفهوم Context آشنا شدید.
- یک Context جدید با
createContextایجاد کردید. - با استفاده از Provider دادهها را در اختیار کل برنامه قرار دادید.
- با هوک
useContextاطلاعات را در کامپوننتهای مختلف دریافت کردید. - State موجود در Context را بهروزرسانی کردید و مشاهده کردید که سایر کامپوننتها بهصورت خودکار بهروزرسانی شدند.
- با بهترین شیوههای استفاده از Context و محدودیتهای آن آشنا شدید.
Context API یکی از مهمترین قابلیتهای React است و در بسیاری از پروژههای واقعی برای مدیریت اطلاعات مشترک مانند احراز هویت، تنظیمات برنامه، زبان و تم استفاده میشود. با استفاده صحیح از این ابزار میتوانید ساختار پروژههای React را سادهتر، خواناتر و قابل نگهداریتر کنید.
از همراهی شما با پارمین کلود سپاسگزاریم.
نظرات کاربران