Redux Middleware: ما هو وكيف تبنيه من الصفر
يُعد Redux Middleware أحد المفاهيم الأساسية التي تمنح Redux مرونة أكبر عند التعامل مع actions قبل وصولها إلى reducer. ومن خلاله يمكنك تسجيل العمليات، معالجة الأخطاء، تنفيذ الطلبات غير المتزامنة، أو حتى تعديل الإجراء نفسه قبل تمريره داخل دورة العمل الخاصة بالتخزين.
في هذا المقال، سنتناول مفهوم middleware في Redux، سبب استخدامه، وطريقة إنشائه من الصفر بأسلوب عملي وواضح يفيد المطورين الذين يعملون مع JavaScript وReact.

ما هو Redux Middleware؟
يشير Redux Middleware إلى طبقة وسيطة تقع بين عملية dispatch وبين وصول action إلى reducer. هذه الطبقة تتيح لك اعتراض كل إجراء يتم إرساله، ثم تنفيذ منطق إضافي عليه مثل:
- تسجيل تفاصيل actions داخل وحدة التحكم.
- إرسال تقارير الأخطاء.
- تنفيذ طلبات API غير المتزامنة.
- تعديل البيانات قبل وصولها إلى reducer.
- منع بعض الإجراءات من الاستمرار عند الحاجة.
بمعنى آخر، يعمل middleware كمرشح ذكي يسمح بإضافة سلوك إضافي دون تلويث reducer أو الإخلال بمبدأ نقاء الدوال داخل Redux.
مثال أساسي لفهم تدفق Redux
قبل الدخول في بناء middleware، لنراجع مثالاً بسيطاً يوضح طريقة عمل Redux دون أي طبقة وسيطة:
import React from "react";
import ReactDOM from "react-dom";
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 });
في هذا المثال، يقوم reducer بتحديث الحالة state بناءً على نوع action والقيمة الموجودة في payload. وكل مرة تتغير فيها الحالة، يتم تنفيذ store.subscribe لعرض القيمة الحالية.
ما هي معاملات createStore؟
تستقبل الدالة createStore في Redux ثلاثة معاملات رئيسية:
- reducer: وهو المعامل الإلزامي.
- initial state: القيمة الابتدائية للحالة، وهو معامل اختياري.
- middleware: ويُستخدم لإضافة الطبقات الوسيطة، وهو أيضاً اختياري.
فهم هذه النقطة مهم، لأن Redux يستطيع التمييز بين initial state وmiddleware بناءً على نوع المعامل الممرّر.
كيفية إنشاء Middleware في Redux
استيراد applyMiddleware
لإنشاء middleware مخصص، نحتاج أولاً إلى استيراد الدالة applyMiddleware من Redux:
import { applyMiddleware } from "redux";
صيغة Middleware الأساسية
لنفترض أننا نريد إنشاء loggerMiddleware بسيط. تكون الصيغة العامة كما يلي:
const loggerMiddleware = (store) => (next) => (action) => {
// your code
};
ويمكن كتابة الصيغة نفسها باستخدام دوال تقليدية بالشكل التالي:
const loggerMiddleware = function (store) {
return function (next) {
return function (action) {
// your code
};
};
};
هذا التركيب المتداخل ليس عشوائياً، بل يعكس آلية عمل Redux في تمرير store أولاً، ثم الدالة next، ثم action الحالي.
تمرير Middleware إلى المتجر
بعد إنشاء middleware، نمرره إلى applyMiddleware:
const middleware = applyMiddleware(loggerMiddleware);
ثم نمرر الناتج إلى createStore:
const store = createStore(reducer, middleware);
ورغم أن middleware يُذكر نظرياً كمعامل ثالث، فإن عدم تمرير initial state يجعل Redux يتعرف تلقائياً إلى أن المعامل الثاني هو طبقة وسيطة.
بناء Logger Middleware عملياً
لنطبق الآن middleware يقوم فقط بعرض action داخل وحدة التحكم قبل تمريره إلى reducer:
const loggerMiddleware = (store) => (next) => (action) => {
console.log("action", action);
next(action);
};
هنا يقوم loggerMiddleware بخطوتين واضحتين:
- طباعة action الحالي.
- تمريره إلى المرحلة التالية باستخدام next(action).
هذا يعني أن middleware لا يمنع سير العملية، بل يضيف فقط سلوكاً إضافياً قبل وصول الإجراء إلى reducer.

ما دور next(action) داخل Middleware؟
الدالة next هي العنصر الفاصل بين تنفيذ middleware وانتقال action إلى الجهة التالية في السلسلة. إذا كانت هناك عدة طبقات middleware، فإن next يمرر الإجراء إلى الطبقة التالية. وإذا كانت هذه آخر طبقة، فسيتم تمريره إلى reducer.
لذلك، استدعاء next(action) هو ما يسمح بإكمال دورة العمل الطبيعية داخل Redux.
ماذا يحدث إذا لم تستدعِ next؟
إذا حذفت استدعاء next(action) من داخل middleware، فإن action سيتوقف عند هذه الطبقة ولن يصل إلى reducer إطلاقاً:
const loggerMiddleware = (store) => (next) => (action) => {
console.log("action", action);
};
في هذه الحالة، ستظهر actions داخل وحدة التحكم، لكن الحالة state لن تتغير، لأن reducer لم يُنفّذ.
وهذا السلوك يشبه إلى حد كبير آلية middleware في Node.js، حيث يؤدي عدم استدعاء next إلى إيقاف انتقال الطلب إلى الخطوة التالية.

تعديل Action قبل وصوله إلى Reducer
من المزايا المهمة في Redux Middleware أنك تستطيع تعديل action قبل تمريره إلى reducer. المثال التالي يوضح تغيير قيمة payload إلى 3 في كل مرة:
const loggerMiddleware = (store) => (next) => (action) => {
console.log("action", action);
action.payload = 3;
next(action);
};
ما الذي سيحدث هنا؟
- سيبقى action.type كما هو.
- لكن payload سيتغير دائماً إلى القيمة 3.
- بالتالي، أي عملية INCREMENT أو DECREMENT ستستخدم الرقم 3 بغض النظر عن القيمة الأصلية.
إذا أرسلت إجراءات مثل:
- INCREMENT بقيمة 1
- INCREMENT بقيمة 5
- DECREMENT بقيمة 2
فإن النتيجة الفعلية ستكون:
- زيادة بمقدار 3
- زيادة ثانية بمقدار 3
- ثم إنقاص بمقدار 3
لتكون القيمة النهائية للحالة هي 3.

لماذا يُسمح بتعديل Action داخل Middleware؟
في هذا السياق، تعديل action داخل middleware لا يُعد مشكلة بحد ذاته، لأن middleware ليس reducer. لكن يجب الانتباه إلى أن reducers يجب أن تبقى pure functions، أي:
- لا تعدّل state مباشرة.
- لا تغيّر action داخل reducer.
- تعيد حالة جديدة بناءً على المدخلات فقط.
الفصل بين منطق الاعتراض والمعالجة من جهة، ومنطق تحديث الحالة من جهة أخرى، هو ما يجعل تطبيق Redux أكثر قابلية للصيانة والتوسّع.
استخدام أكثر من Middleware في Redux
لا يقتصر Redux على طبقة وسيطة واحدة. يمكنك تعريف عدة طبقات وتمريرها جميعاً إلى applyMiddleware بالشكل التالي:
const middleware = applyMiddleware(
loggerMiddleware,
secondMiddleware,
thirdMiddleware
);
سيتم تنفيذ جميع middlewares بالتسلسل، واحداً تلو الآخر. وهذا يمنحك مرونة كبيرة لتوزيع المسؤوليات، مثل:
- loggerMiddleware لتسجيل الأحداث.
- errorMiddleware لالتقاط الأخطاء.
- asyncMiddleware أو redux-thunk للتعامل مع العمليات غير المتزامنة.
متى تحتاج إلى Redux Middleware فعلياً؟
يصبح استخدام Redux Middleware مهماً عندما يتجاوز التطبيق مجرد تحديثات بسيطة للحالة، خصوصاً في الحالات التالية:
- عند الحاجة إلى تتبع جميع actions لأغراض التصحيح.
- عند تنفيذ طلبات API قبل تحديث الواجهة.
- عند التحقق من صلاحيات المستخدم قبل تنفيذ إجراء معين.
- عند بناء منطق مركزي للتعامل مع الأخطاء أو التحليلات.
وباختصار، كلما زاد تعقيد التطبيق، أصبحت middleware أداة أساسية لتنظيم منطق العمل خارج components وreducers.
أفضل الممارسات عند كتابة Middleware مخصص
- اجعل middleware مسؤولاً عن مهمة واضحة ومحددة.
- لا تُحمّله منطقاً ضخماً يصعب اختباره وصيانته.
- استدعِ next(action) ما لم تكن تنوي عمداً إيقاف الإجراء.
- تجنب التعديلات غير المتوقعة على البيانات إلا إذا كان ذلك ضرورياً ومفهوماً ضمن سياق التطبيق.
- افصل بين طبقات التسجيل، الأخطاء، والعمليات غير المتزامنة لتحسين القابلية لإعادة الاستخدام.
الخلاصة التقنية
Redux Middleware ليس مجرد إضافة اختيارية، بل هو نقطة تحكم استراتيجية داخل دورة Redux. فهو يتيح اعتراض actions، تنفيذ منطق جانبي، أو تعديل البيانات قبل وصولها إلى reducer، مع الحفاظ على نقاء reducers وتنظيم بنية التطبيق. ومن الناحية العملية، كلما أردت توسيع قدرات Redux بطريقة احترافية وقابلة للصيانة، فإن فهم middleware وبناؤه من الصفر يُعد خطوة أساسية لا غنى عنها.