تعلّم Redux عملياً عبر بناء تطبيق عدّاد في React
مقدمة: لماذا يُعد Redux مهماً في تطبيقات الواجهة الأمامية؟
تُعد مكتبة Redux واحدة من أشهر أدوات إدارة الحالة في تطبيقات الواجهة الأمامية. وغالباً ما تُستخدم مع React من خلال الحزمة react-redux، لكنها ليست مرتبطة بها حصراً، إذ يمكن تشغيلها أيضاً مع أي إطار عمل أو حتى مع Vanilla JavaScript.
في البداية، قد يبدو Redux معقداً بعض الشيء، خصوصاً بسبب كثرة الملفات والأنماط التنظيمية التي يتطلبها. لهذا يصفه بعض المطورين بأنه مبالغ فيه عند استخدامه في التطبيقات الصغيرة. ومع ذلك، يبقى مفيداً جداً عندما تحتاج إلى إدارة حالة مشتركة بين عدة مكونات بطريقة منظمة وقابلة للتوسع.
في تطبيقات React التقليدية، يتم تمرير البيانات عبر props من الأعلى إلى الأسفل. لكن عند ازدياد حجم المشروع، تصبح هذه الطريقة مرهقة. هنا يأتي دور Redux، حيث يتم تخزين الحالة في store عام يمكن الوصول إليه من أي مكان داخل التطبيق.
في هذا الدليل، سنبني تطبيق عدّاد بسيطاً باستخدام Redux داخل مشروع React، مع إضافة مثال بسيط على تسجيل الدخول الوهمي لتوضيح فكرة إدارة أكثر من حالة في الوقت نفسه.

إعداد المشروع وتثبيت الحزم المطلوبة
لبداية العمل، ثبّت حزمتَي redux وreact-redux عبر مدير الحزم NPM:
npm i redux react-redux
الحزمة redux مسؤولة عن منطق إدارة الحالة، بينما تمنحنا react-redux أدوات الربط مع React مثل Provider وuseSelector وuseDispatch.
تنظيم الملفات والمجلدات داخل المشروع
من الأفضل إنشاء مجلد باسم redux ليحتوي على كل ما يتعلق بالإدارة المركزية للحالة. وداخل هذا المجلد يمكن إنشاء:
- مجلد
actionsلتعريف الأوامر المرسلة. - مجلد
reducersلمعالجة الحالة بناءً على نوع الإجراء.
هذه البنية تجعل المشروع أوضح وأسهل في الصيانة، خاصة عند التوسع لاحقاً.
إنشاء Reducer خاص بالعدّاد
الـ Reducer هو دالة تستقبل الحالة الحالية وaction جديداً، ثم تُعيد الحالة الجديدة بناءً على نوع العملية المطلوبة. لنبدأ بملف باسم counterReducer.js داخل مجلد reducers:
const counterReducer = (state = 1, action) => {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
case "RESET":
return (state = 0);
default:
return state;
}
};
export default counterReducer;
في هذا المثال، تبدأ قيمة الحالة من 1، ويمكنك تغييرها إلى 0 إذا كان ذلك أنسب لمنطق تطبيقك.
ما الذي يفعله هذا الكود؟
INCREMENT: يزيد العداد بمقدار واحد.DECREMENT: ينقص العداد بمقدار واحد.RESET: يعيد العداد إلى الصفر.
استخدام الأحرف الكبيرة في قيم type هو أسلوب شائع لتسهيل التمييز بين أنواع الإجراءات، لكنه ليس إلزامياً.
إضافة Reducer للمصادقة الوهمية
لجعل المثال أكثر فائدة، سنضيف حالة مصادقة بسيطة توضح كيف يمكن لـ Redux إدارة أكثر من جزء من الحالة في تطبيق واحد. أنشئ ملفاً باسم authReducer.js وضع فيه الكود التالي:
const authReducer = (state = false, action) => {
switch (action.type) {
case "LOG_IN":
return true;
case "LOG_OUT":
return false;
default:
return state;
}
};
export default authReducer;
هنا تبدأ الحالة بالقيمة false، أي أن المستخدم غير مسجل الدخول. وعند إرسال LOG_IN تتحول القيمة إلى true، بينما تعيدها LOG_OUT إلى false.
دمج Reducers باستخدام combineReducers
بما أننا نملك أكثر من Reducer، نحتاج إلى دمجهما في دالة واحدة. لهذا نستخدم المساعد combineReducers من مكتبة redux. أنشئ ملف index.js داخل مجلد reducers:
import counter from "./counter";
import auth from "./auth";
import { combineReducers } from "redux";
const allReducers = combineReducers({
counter,
auth,
});
export default allReducers;
النتيجة هنا هي Reducer موحد يحتوي على مفتاحين:
counterللحالة الخاصة بالعداد.authللحالة الخاصة بالمصادقة.
إنشاء المتجر العام Store
الخطوة التالية هي إنشاء store عام يحتوي على الحالة الكاملة للتطبيق. غالباً ما يتم ذلك داخل ملف index الرئيسي لمشروع React:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { createStore } from "redux";
import allReducers from "./redux/reducers";
import { Provider } from "react-redux";
const store = createStore(allReducers);
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
دور Provider في هذا السياق
العنصر Provider يربط تطبيق React بالمتجر العام، ما يسمح لأي مكوّن داخله بالوصول إلى الحالة أو إرسال إجراءات جديدة دون الحاجة إلى تمرير البيانات يدوياً بين المكونات.
إنشاء Actions الخاصة بالعداد وتسجيل الدخول
الـ Actions هي دوال تُرجع كائنات تصف ما الذي يجب أن يحدث داخل التطبيق. داخل ملف index.js في مجلد actions، أضف التعريفات التالية:
export const increment = () => {
return {
type: "INCREMENT",
};
};
export const decrement = () => {
return {
type: "DECREMENT",
};
};
export const reset = () => {
return {
type: "RESET",
};
};
export const logIn = () => {
return {
type: "LOG_IN",
};
};
export const logOut = () => {
return {
type: "LOG_OUT",
};
};
هذه الدوال لا تغيّر الحالة مباشرة، بل تُرسل وصفاً للعملية، ثم يتولى Reducer تنفيذ التحديث المناسب.
قراءة الحالة وإرسال الأوامر داخل App.js
للوصول إلى الحالة العامة نستخدم useSelector، ولإرسال الإجراءات نستخدم useDispatch. بعد ذلك نربط الأزرار بالوظائف المناسبة:
import "./App.css";
import { useSelector, useDispatch } from "react-redux";
import {
decrement,
increment,
reset,
logIn,
logOut,
} from "./redux/actions/index";
function App() {
const counter = useSelector((state) => state.counter);
const auth = useSelector((state) => state.auth);
const dispatch = useDispatch();
return (
<div className="App">
<h1>
Hello World <br />
A little Redux Project. YaaY!
</h1>
<h3>Counter</h3>
<h3>{counter}</h3>
<button onClick={() => dispatch(increment())}>Increase</button>
<button onClick={() => dispatch(reset())}>Reset</button>
<button onClick={() => dispatch(decrement())}>Decrease</button>
<h2>For Logged in users only</h2>
<p>Log in to see a secret about me</p>
<button onClick={() => dispatch(logIn())}>Login</button>
<button onClick={() => dispatch(logOut())}>Logout</button>
{auth ? (
<div>
<p>
I don't more than 50% of redux. But if you know 50% of it,
you're like a Superman.
</p>
</div>
) : (
""
)}
</div>
);
}
export default App;
كيف يعمل هذا الجزء؟
useSelectorيقرأ القيم من الحالة العامة.useDispatchيعيد دالةdispatchالمستخدمة لإرسال الإجراءات.- عند النقر على أي زر، يتم استدعاء
dispatch()مع الإجراء المناسب. - إذا كانت قيمة
authتساويtrue، يظهر المحتوى المحمي.
فهم تدفق البيانات في Redux
لفهم الصورة الكاملة، يمكن تبسيط سير العمل في Redux على النحو التالي:
- المستخدم ينفذ إجراءً مثل النقر على زر.
- يتم استدعاء دالة
action creator. - يُرسل
dispatchالكائن إلىReducer. - يقوم
Reducerبإرجاع حالة جديدة. - تُحدّث الواجهة تلقائياً بناءً على الحالة الجديدة.
هذا التدفق المنظم يجعل تتبع التغييرات أسهل، خصوصاً في التطبيقات التي تحتوي على منطق معقد أو شاشات متعددة.
متى تستخدم Redux ومتى تتجنبه؟
ليست كل المشاريع بحاجة إلى Redux. في المشاريع الصغيرة، قد تكفي أدوات أبسط مثل props أو Context داخل React. أما في التطبيقات الكبيرة التي تتشارك فيها عدة مكونات نفس البيانات، فإن Redux يقدم تنظيماً أعلى وسهولة أكبر في التحكم بالحالة.
| الحالة | الخيار الأنسب غالباً |
|---|---|
| تطبيق صغير بعدد مكونات محدود | props أو Context |
| تطبيق متوسط إلى كبير مع حالة مشتركة معقدة | Redux |
| مشروع يحتاج إلى تتبع واضح للتغييرات | Redux |
النتيجة النهائية المتوقعة
بعد تطبيق الخطوات السابقة، ستحصل على واجهة بسيطة تعرض قيمة العداد، مع أزرار للزيادة والإنقاص وإعادة الضبط، إضافة إلى جزء خاص بالمستخدمين المسجلين دخولهم فقط.

نصائح عملية لتحسين جودة مشروعك عند استخدام Redux
- ابدأ ببنية بسيطة، ثم وسّعها فقط عند الحاجة.
- حافظ على تسمية واضحة للملفات مثل
actionsوreducers. - اجعل كل
Reducerمسؤولاً عن جزء محدد من الحالة. - تجنب إدخال منطق معقد داخل المكونات إذا كان مكانه الطبيعي داخل إدارة الحالة.
- اختبر تدفق البيانات جيداً حتى لا تختلط عليك مصادر التحديث.
الخلاصة التقنية
يُعد Redux خياراً ممتازاً عندما تحتاج إلى إدارة حالة مركزية بشكل منظم وقابل للتوسع داخل تطبيقات React. ورغم أن استخدامه في المشاريع الصغيرة قد يبدو زائداً عن الحاجة، فإن فهم أساسياته مثل store وactions وreducers يمنحك قاعدة قوية للتعامل مع التطبيقات الأكبر بثقة وكفاءة. بناء تطبيق عدّاد هو نقطة انطلاق ممتازة لفهم الفكرة، ثم الانتقال لاحقاً إلى حالات استخدام أكثر واقعية وتعقيداً.