تعلّم Redux عملياً عبر بناء تطبيق عدّاد في React

دقائق القراءة: 6

مقدمة: لماذا يُعد Redux مهماً في تطبيقات الواجهة الأمامية؟

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

في البداية، قد يبدو Redux معقداً بعض الشيء، خصوصاً بسبب كثرة الملفات والأنماط التنظيمية التي يتطلبها. لهذا يصفه بعض المطورين بأنه مبالغ فيه عند استخدامه في التطبيقات الصغيرة. ومع ذلك، يبقى مفيداً جداً عندما تحتاج إلى إدارة حالة مشتركة بين عدة مكونات بطريقة منظمة وقابلة للتوسع.

في تطبيقات React التقليدية، يتم تمرير البيانات عبر props من الأعلى إلى الأسفل. لكن عند ازدياد حجم المشروع، تصبح هذه الطريقة مرهقة. هنا يأتي دور Redux، حيث يتم تخزين الحالة في store عام يمكن الوصول إليه من أي مكان داخل التطبيق.

في هذا الدليل، سنبني تطبيق عدّاد بسيطاً باستخدام Redux داخل مشروع React، مع إضافة مثال بسيط على تسجيل الدخول الوهمي لتوضيح فكرة إدارة أكثر من حالة في الوقت نفسه.

شرح عملي لمكتبة ريدكس 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 على النحو التالي:

  1. المستخدم ينفذ إجراءً مثل النقر على زر.
  2. يتم استدعاء دالة action creator.
  3. يُرسل dispatch الكائن إلى Reducer.
  4. يقوم Reducer بإرجاع حالة جديدة.
  5. تُحدّث الواجهة تلقائياً بناءً على الحالة الجديدة.

هذا التدفق المنظم يجعل تتبع التغييرات أسهل، خصوصاً في التطبيقات التي تحتوي على منطق معقد أو شاشات متعددة.

متى تستخدم Redux ومتى تتجنبه؟

ليست كل المشاريع بحاجة إلى Redux. في المشاريع الصغيرة، قد تكفي أدوات أبسط مثل props أو Context داخل React. أما في التطبيقات الكبيرة التي تتشارك فيها عدة مكونات نفس البيانات، فإن Redux يقدم تنظيماً أعلى وسهولة أكبر في التحكم بالحالة.

الحالة الخيار الأنسب غالباً
تطبيق صغير بعدد مكونات محدود props أو Context
تطبيق متوسط إلى كبير مع حالة مشتركة معقدة Redux
مشروع يحتاج إلى تتبع واضح للتغييرات Redux

النتيجة النهائية المتوقعة

بعد تطبيق الخطوات السابقة، ستحصل على واجهة بسيطة تعرض قيمة العداد، مع أزرار للزيادة والإنقاص وإعادة الضبط، إضافة إلى جزء خاص بالمستخدمين المسجلين دخولهم فقط.

النتيجة النهائية لتطبيق عداد باستخدام Redux وReact مع مثال على تسجيل الدخول

نصائح عملية لتحسين جودة مشروعك عند استخدام Redux

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

الخلاصة التقنية

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

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *