آموزش اشتراک‌گذاری State بین کامپوننت‌های React با استفاده از Context

مقدمه

در برنامه‌های 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;

نتیجه

اکنون هر دو کامپوننت:

  • Header
  • Profile

از یک 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 را ساده‌تر، خواناتر و قابل نگهداری‌تر کنید.

 

 

از همراهی شما با پارمین کلود سپاسگزاریم.

Click to rate this post!
[Total: 0 Average: 0]

نظرات کاربران

دیدگاهی بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *