ما یاد گرفتیم که Reducer چطور منطق بهروزرسانی استیت را یکپارچه میکند و Context چطور به ما اجازه میدهد دادهها را بدون درگیر شدن با کلاف سردرگمِ Prop Drilling به اعماق درخت کامپوننتها ارسال کنیم.
حالا زمان آن رسیده که این دو ابزار قدرتمند را با هم ترکیب کنیم. این ترکیب طلایی، یکی از بهترین و استانداردترین روشها برای مدیریت استیتهای پیچیده در اپلیکیشنهای ریآکتی (بدون نیاز به کتابخانههای جانبی مثل Redux) است.
نحوه تلفیق هوشمندانه ردیوسر با سیستم Context
حذف کامل انتقال استیت و توابع dispatch از طریق پروپها
پیادهسازی الگوی کامپوننت ارائهدهنده (Provider) و ساخت هوکهای سفارشی (Custom Hooks) در یک فایل مجزا
در پروژههای واقعی، ردیوسر استیت را در بالاترین سطح کامپوننت مدیریت میکند. اما برای اینکه فرزندان در اعماق درخت بتوانند این دیتای زنده را بخوانند یا اکشنی را دیسپچ کنند، مجبوریم هم خودِ state و هم تابع dispatch را لایه به لایه پایین بفرستیم.
وقتی تعداد کامپوننتهای واسط به دهها یا صدها مورد میرسد، پروپ دریلینگ امان شما را میبرد! راهکار نهایی، تلهپورت کردن همزمان دیتای استیت و تابع تغییر استیت (dispatch) به کمک Context است.
برای این کار، ۳ گام ساده را طی میکنیم:
ایجاد بافتهای داده (Contexts)
تزریق دیتای ردیوسر به کامپوننتهای ارائهدهنده
مصرف مستقیم در هر کجای برنامه
بیایید یک سیستم مدیریت پروژه کوچک بسازیم. تمام فرآیندها را در یک فایل اختصاصی به نام ProjectContext.js متمرکز میکنیم تا کدهای کامپوننتهای دیگر کاملاً پاکیزه بمانند.
بهترین ساختار این است که دو Context مجزا بسازیم؛ یکی برای خواندن دیتا و دیگری برای دیسپچ کردن اکشنها. این تفکیک باعث میشود کامپوننتی که فقط نیاز به دیسپچ کردن یک دستور دارد، با تغییر دیتای اصلی بیهوده دوباره رندر (Re-render) نشود.
// ProjectContext.js
import { createContext, useContext, useReducer } from 'react';
// ۱. ساخت بافتهای داده مجزا
const ProjectsContext = createContext(null);
const ProjectsDispatchContext = createContext(null);
// ۲. تعریف ردیوسر (منطق آپدیت استیت)
function projectsReducer(projects, action) {
switch (action.type) {
case 'added':
return [...projects, { id: action.id, title: action.title }];
case 'deleted':
return projects.filter(p => p.id !== action.id);
default:
throw Error('اکشن نامعتبر: ' + action.type);
}
}
// ۳. کامپوننت اصلی کامپایلکننده (TasksProvider)
export function ProjectsProvider({ children }) {
const [projects, dispatch] = useReducer(projectsReducer, []);
return (
<ProjectsContext value={projects}>
<ProjectsDispatchContext value={dispatch}>
{children}
</ProjectsDispatchContext>
</ProjectsContext>
);
}
// ۴. ساخت هوکهای اختصاصی برای راحتی کار در فرزندان (Custom Hooks)
export function useProjects() {
return useContext(ProjectsContext);
}
export function useProjectsDispatch() {
return useContext(ProjectsDispatchContext);
}
حالا کامپوننت اصلی شما (App) عملاً هیچ بار سنگینی به دوش نمیکشد و صرفاً محیط نمایش را با ProjectsProvider محصور میکند:
// App.js
import { ProjectsProvider } from './ProjectContext.js';
import AddProject from './AddProject.js';
import ProjectList from './ProjectList.js';
export default function App() {
return (
<ProjectsProvider>
<h1>داشبورد مدیریت پروژهها</h1>
<AddProject />
<ProjectList />
</ProjectsProvider>
);
}
حالا به سراغ کامپوننت فرزند یعنی AddProject میرویم. این کامپوننت اصلاً نیازی به لیست پروژهها ندارد و فقط میخواهد دکمه اضافه کردن را اعمال کند. به کمک هوک سفارشی ما، مستقیماً به dispatch وصل میشود:
// AddProject.js
import { useState } from 'react';
import { useProjectsDispatch } from './ProjectContext.js';
let nextId = 0;
export default function AddProject() {
const [title, setTitle] = useState('');
const dispatch = useProjectsDispatch(); // مصرف مستقیم بدون نیاز به دریافت پروپ از پدر
return (
<div className="add-form">
<input value={title} onChange={e => setTitle(e.target.value)} />
<button onClick={() => {
dispatch({ type: 'added', id: nextId++, title: title });
setTitle('');
}}>
افزودن پروژه جدید
</button>
</div>
);
}
به همین ترتیب، کامپوننت لیست (ProjectList) بدون اینکه پروپ دریافت کند، از هوک useProjects برای رندر کردن آیتمها استفاده خواهد کرد.
کاهش شلوغی کدهای واجهه کاربر: کامپوننتها فقط روی چیزی که قرار است رندر کنند تمرکز دارند و کاری به این ندارند که داده چطور ساخته و پردازش میشود.
ساختار کاستوم هوکها (Custom Hooks): توابعی مثل useProjects کار را برای تیم توسعه بسیار راحت میکنند؛ نیازی نیست هر بار دو فایل و دو هوک را ایمپورت کنند، همهچیز با یک خط کد لود میشود.
قابلیت استفاده چندگانه: شما میتوانید از ProjectsProvider در بخشهای مختلف برنامه استفاده کنید و هر بخش استیت مستقل و ایزوله خودش را داشته باشد.
ترکیب ردیوسر و کانتکست به هر کامپوننتی در اعماق درخت اجازه میدهد تا وضعیت سراسری را بخواند و آن را بهروزرسانی کند.
همیشه دو کانتکست مجزا بسازید: یکی برای داده استیت و دیگری برای تابع dispatch جهت بهینهسازی رندرها.
برای تمیزی کار، تمام این اتصالات را داخل یک فایل ببرید، یک کامپوننت Wrapper به عنوان ارائهدهنده بسازید و هوکهای اختصاصی با پیشوند use خروجی بگیرید.
برنامه شما میتواند بر حسب نیاز، چندین زوجِ مستقل کانتکست-ردیوسر در لایههای مختلف داشته باشد.
این محتوا کاملا رایگان توسط تیم کدلپر ترجمه شده و در اختیار شما کاربران عزیز قرار گرفته است، هر گونه کپی برداری برای مقاصد غیر رایگان و بدون ذکر منبع، مورد پیگیری قانونی قرار میگیرد.
ترجمه شده از منبع: https://react.dev/learn