تعلم أساسيات Redux للمبتدئين مع أمثلة عملية بلغة JavaScript
قد تبدو مكتبة Redux معقدة في البداية، خصوصاً لمن يبدأون رحلتهم مع React. فهناك مجموعة من المفاهيم التي يجب فهمها جيداً مثل store وactions وreducers وpure functions وimmutability. ومع ذلك، تبقى معرفة أساسيات Redux مهارة مهمة لأي مطور واجهات حديثة، لأن كثيراً من المشاريع الكبيرة تعتمد عليها لتنظيم الحالة وإدارة البيانات بشكل واضح وقابل للتوسع.
في هذا المقال ستتعرف على الفكرة الأساسية وراء Redux، وكيفية إنشاء متجر بيانات بسيط، ثم تعديل الحالة عبر إرسال الإجراءات، مع أمثلة عملية سهلة الفهم يمكنك البناء عليها لاحقاً في مشاريعك.
ما هي Redux ولماذا تُستخدم؟
مكتبة Redux هي أداة لإدارة الحالة State Management داخل التطبيقات. هدفها الأساسي هو جعل البيانات في التطبيق أكثر تنظيماً، خاصة عندما تكبر الواجهة وتزداد المكونات التي تحتاج إلى مشاركة نفس الحالة.
ورغم أن كثيرين يربطون Redux مباشرةً مع React، فإنها ليست مخصصة له وحده. يمكن استخدامها مع Angular وVue وحتى مع Vanilla JavaScript. لكن شهرتها الواسعة جاءت من استخدامها المكثف داخل تطبيقات React.
أهم ما يميز Redux أنها تعتمد على متجر مركزي واحد يحتفظ بحالة التطبيق، ما يساعد على:
- تتبع التغييرات بسهولة.
- تقليل الفوضى الناتجة عن تمرير البيانات بين المكونات.
- تسهيل الاختبار والصيانة.
- جعل تدفق البيانات أكثر قابلية للتوقع.
كيف تبدأ مشروعاً بسيطاً لتعلم Redux؟
لبداية عملية، أنشئ مشروع React جديداً باستخدام create-react-app عبر الأمر التالي:
npx create-react-app redux-demo
يسمح الأمر npx بتشغيل الحزمة create-react-app دون الحاجة إلى تثبيتها بشكل دائم على جهازك.
بعد إنشاء المشروع، احذف الملفات الموجودة داخل مجلد src، ثم أنشئ ملفاً جديداً باسم index.js داخل المجلد نفسه.
بعد ذلك ثبّت مكتبة Redux من داخل مجلد المشروع:
npm install redux@4.1.0
بهذا أصبح المشروع جاهزاً لتجربة المفاهيم الأساسية.
إنشاء متجر Redux لأول مرة
في Redux، يُعد store المكان الذي تُخزن فيه حالة التطبيق. ولإنشائه نحتاج إلى استيراد الدالة createStore:
import { createStore } from 'redux';
تقبل الدالة createStore ثلاثة معاملات:
reducerوهو معامل إلزامي.- القيمة الابتدائية للحالة، وهو اختياري.
enhancerلتمرير أدوات إضافية مثلmiddleware، وهو اختياري.
إليك مثالاً أولياً:
import { createStore } from 'redux';
const reducer = (state, action) => {
console.log('reducer called');
return state;
};
const store = createStore(reducer, 0);
في هذا المثال أنشأنا دالة reducer تُعيد الحالة كما هي دون أي تعديل. ثم مررناها إلى createStore مع قيمة ابتدائية تساوي 0.
لكن الأسلوب الأكثر شيوعاً هو تهيئة الحالة مباشرة داخل reducer باستخدام المعاملات الافتراضية في ES6:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
console.log('reducer called');
return state;
};
const store = createStore(reducer);
هذا الأسلوب أوضح وأسهل عند إدارة حالات أكثر تعقيداً لاحقاً.
الاستماع إلى تغيّرات المتجر
بعد إنشاء store، يمكن استخدام الدالة subscribe() لمراقبة أي تغيير يطرأ على الحالة:
store.subscribe(() => {
console.log('current state', store.getState());
});
هنا نسجل دالة تُنفذ تلقائياً كلما تغيّرت قيمة الحالة داخل store، ونستخدم store.getState() للحصول على القيمة الحالية.
يمكنك الآن وضع الكود الكامل التالي داخل ملف src/index.js:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
console.log('reducer called');
return state;
};
const store = createStore(reducer);
store.subscribe(() => {
console.log('current state', store.getState());
});
عند تشغيل المشروع بالأمر npm start، ستلاحظ ظهور الرسالة reducer called في وحدة التحكم، لأن Redux يستدعي reducer مباشرة عند إنشاء المتجر.

كيف تغيّر الحالة داخل Redux؟
إنشاء المتجر وحده لا يكفي. الفائدة الحقيقية تظهر عندما نبدأ بتعديل الحالة. والطريقة الوحيدة لتغيير الحالة في Redux هي إرسال إجراء action باستخدام الدالة dispatch().
مثال بسيط:
store.dispatch({ type: 'INCREMENT' });
الإجراء action هو كائن object يجب أن يحتوي على الخاصية type. هذه الخاصية تحدد نوع العملية المطلوبة، مثل INCREMENT أو DECREMENT أو ADD_USER.
ومن الأفضل كتابة قيم type بأحرف كبيرة لتكون أكثر وضوحاً واتساقاً داخل المشروع.
مثال عملي على الزيادة والنقصان
استبدل محتوى ملف index.js بالكود التالي:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
if (action.type === 'INCREMENT') {
return state + 1;
} else if (action.type === 'DECREMENT') {
return state - 1;
}
return state;
};
const store = createStore(reducer);
store.subscribe(() => {
console.log('current state', store.getState());
});
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'INCREMENT' });
store.dispatch({ type: 'DECREMENT' });
عند تشغيل التطبيق، ستلاحظ تغيّر الحالة بعد كل عملية إرسال.

ما يحدث هنا ببساطة هو أن reducer يستقبل الحالة الحالية والإجراء الجديد، ثم يحدد الحالة التالية ويُعيدها. والقيمة التي يُرجعها تصبح هي الحالة الجديدة داخل store.
لماذا يجب عدم تعديل الحالة الأصلية مباشرة؟
من أهم قواعد Redux عدم تعديل الحالة الأصلية بشكل مباشر داخل reducer. يجب دائماً إنشاء قيمة جديدة وإرجاعها بدلاً من العبث بالقيمة الحالية.
هذا المثال غير مستحب:
if (action.type === 'INCREMENT') {
state = state + 1;
return state;
}
رغم أن المثال قد يبدو بسيطاً هنا، فإن الفكرة العامة في Redux تقوم على immutability، أي التعامل مع الحالة على أنها قيمة لا ينبغي تعديلها مباشرة، بل استبدالها بنسخة جديدة. هذا الأسلوب يقلل الأخطاء، ويسهّل التتبع، ويحسن إمكانية الاختبار.
استخدام switch بدلاً من if...else
في التطبيقات الواقعية، يُفضل كثير من المطورين استخدام switch داخل reducer بدلاً من سلسلة if...else لأنها أوضح عندما تتعدد أنواع الإجراءات.
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
وجود الحالة الافتراضية default ضروري جداً حتى يُعاد state السابق إذا لم يتطابق نوع الإجراء مع أي حالة معروفة.
تمرير بيانات إضافية داخل الإجراء باستخدام payload
لا يقتصر action على الخاصية type فقط، بل يمكنه حمل بيانات إضافية نستخدمها أثناء تحديث الحالة. وأكثر اسم شائع لهذا الجزء هو payload.
جرّب الكود التالي:
import { createStore } from 'redux';
const reducer = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + action.payload;
case 'DECREMENT':
return state - action.payload;
default:
return state;
}
};
const store = createStore(reducer);
store.subscribe(() => {
console.log('current state', store.getState());
});
store.dispatch({ type: 'INCREMENT', payload: 1 });
store.dispatch({ type: 'INCREMENT', payload: 5 });
store.dispatch({ type: 'DECREMENT', payload: 2 });
في هذا المثال، لم نعد نزيد أو ننقص بمقدار ثابت، بل جعلنا مقدار التغيير يأتي من الخاصية payload.

تسلسل التنفيذ سيكون كالتالي:
- إرسال
{ type: 'INCREMENT', payload: 1 }يجعل الحالة تصبح1. - إرسال
{ type: 'INCREMENT', payload: 5 }يرفعها إلى6. - إرسال
{ type: 'DECREMENT', payload: 2 }يخفضها إلى4.
صحيح أن اسم payload ليس إلزامياً من ناحية اللغة، لكنه متعارف عليه بين المطورين، لذلك يُستحسن الالتزام به لسهولة القراءة والتعاون داخل الفريق.
المفاهيم الأساسية التي يجب أن تخرج بها من هذا الدرس
إذا أردنا تلخيص أساسيات Redux في نقاط واضحة، فهذه أهم الأفكار:
storeهو المصدر المركزي للحالة.reducerدالة تحدد كيف تتغير الحالة بناءً علىaction.dispatch()هي الطريقة الوحيدة لطلب تغيير الحالة.- كل
actionيجب أن يحتوي علىtype. - يمكن تمرير بيانات إضافية عبر
payload. - يجب عدم تعديل الحالة الأصلية مباشرة داخل
reducer. subscribe()تتيح مراقبة التغيرات التي تحدث في المتجر.
متى يكون استخدام Redux مناسباً؟
ليست كل التطبيقات بحاجة إلى Redux. ففي المشاريع الصغيرة، قد تكون أدوات React المدمجة مثل useState وuseContext كافية. لكن عندما تصبح الحالة:
- كبيرة ومتشعبة،
- موزعة بين مكونات عديدة،
- مرتبطة بتدفقات عمل متعددة،
- بحاجة إلى قابلية أعلى للتتبع والاختبار،
فإن Redux تصبح خياراً عملياً ومنظماً للغاية.
الخلاصة التقنية
تمنحك Redux نموذجاً واضحاً لإدارة الحالة يعتمد على متجر مركزي، وإجراءات معلنة، ومبدلات تتعامل مع البيانات بطريقة قابلة للتوقع. ورغم أن تعلمها في البداية قد يبدو نظرياً بعض الشيء، فإن فهم مفاهيم مثل store وdispatch() وreducer وpayload يضعك على أساس قوي لبناء تطبيقات أكبر وأكثر تنظيماً. من الناحية التقنية، تكمن قوة Redux في فرضها نمطاً منضبطاً للتعامل مع الحالة، وهو ما ينعكس مباشرة على سهولة الصيانة وتقليل الأخطاء في المشاريع المتوسطة والكبيرة.