مثال عن Redux Toolkit

سأقسمه لثلاثة ملفات:

  1. counterSlice.js → تعريف الحالة (state) والدوال (reducers).
  2. store.js → تعريف الـ store وربطه بالـ slice.
  3. Counter.jsx → كمبوننت React يستخدم Redux.
1️⃣ ملف الـ Slice: الجمع والطرح

ملف: counterSlice.js

// 1) نستورد createSlice من Redux Toolkit
import { createSlice } from '@reduxjs/toolkit';

// 2) نعرّف الحالة الابتدائية (initial state)
const initialState = {
  value: 0, // هذا هو العداد، يبدأ من 0
};

// 3) نستخدم createSlice لإنشاء slice
const counterSlice = createSlice({
  name: 'counter', // اسم هذا الجزء من الـ state (مهم للتنظيم)
  initialState,    // الحالة الابتدائية
  reducers: {
    // 4) دالة لزيادة القيمة
    increment: (state) => {
      // في Redux Toolkit نكتب كأننا نعدّل مباشرة
      state.value = state.value + 1;
      // داخليًا يتم عمل نسخة جديدة (immutable) تلقائيًا
    },

    // 5) دالة لإنقاص القيمة
    decrement: (state) => {
      state.value = state.value - 1;
    },

    // 6) دالة لتغيير القيمة بقيمة معيّنة (اختياري)
    incrementByAmount: (state, action) => {
      // action.payload هي القيمة المرسلة من الكمبوننت
      state.value = state.value + action.payload;
    },
  },
});

// 7) نخرج (export) الـ actions الجاهزة للاستخدام في الكمبوننت
export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// 8) نخرج الـ reducer ليُستخدم في الـ store
export default counterSlice.reducer;
JavaScript
شرح سريع لما حدث هنا:
  • createSlice:
    • يجمع لك في مكان واحد:
      • اسم الجزء (name)
      • الحالة (initialState)
      • الدوال التي تعدّل الحالة (reducers)
    • ويولّد تلقائيًا actions بنفس أسماء الدوال (increment, decrement…).
2️⃣ ملف الـ Store وربطه بالـ Slice

ملف: store.js

// 1) نستورد configureStore من Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';

// 2) نستورد الـ reducer الذي أنشأناه في counterSlice
import counterReducer from './counterSlice';

// 3) ننشئ الـ store
export const store = configureStore({
  reducer: {
    // اسم الحقل في الـ state: الـ reducer المسؤول عنه
    counter: counterReducer,
  },
});
JavaScript
ما الذي حصل هنا؟
  • configureStore هو الطريقة الموصى بها لإنشاء store في Redux Toolkit.
  • الخاصية reducer عبارة عن كائن:
    • المفتاح: اسم الجزء من الـ state (counter)
    • القيمة: الـ reducer الخاص به (counterReducer)
  • في التطبيق، الـ state سيبدو هكذا: { counter: { value: 0 } }
3️⃣ ربط الـ store مع تطبيق React

عادة في ملف main.jsx أو index.jsx

import React from 'react';
import ReactDOM from 'react-dom/client';

// 1) نستورد Provider من react-redux لربط React مع Redux
import { Provider } from 'react-redux';

// 2) نستورد الـ store
import { store } from './store';

// 3) نستورد الكمبوننت الرئيسي
import App from './App';

ReactDOM.createRoot(document.getElementById('root')).render(
  // 4) نلف التطبيق كله بـ Provider ونعطيه الـ store
  <Provider store={store}>
    <App />
  </Provider>
);
JavaScript

الفكرة هنا:

  • Provider يجعل الـ store متاحًا لكل كمبوننت داخل <App />.
  • أي كمبوننت تقدر:
    • تقرأ من الـ state
    • ترسل actions لتعديل الـ state
4️⃣ كمبوننت العداد (الجمع والطرح)

ملف: Counter.jsx

import React, { useState } from 'react';

// 1) useSelector: لقراءة البيانات من الـ store
//    useDispatch: لإرسال actions
import { useSelector, useDispatch } from 'react-redux';

// 2) نستورد الـ actions من الـ slice
import { increment, decrement, incrementByAmount } from './counterSlice';

function Counter() {
  // 3) نقرأ القيمة الحالية من الـ state
  // state.counter.value لأننا سجلناه باسم counter في store.js
  const count = useSelector((state) => state.counter.value);

  // 4) نجهز dispatch لإرسال actions
  const dispatch = useDispatch();

  // 5) حالة محلية لإدخال رقم معين
  const [amount, setAmount] = useState(0);

  return (
    <div>
      <h1>القيمة الحالية: {count}</h1>

      {/* 6) زر الجمع */}
      <button onClick={() => dispatch(increment())}>
        + زيادة 1
      </button>

      {/* 7) زر الطرح */}
      <button onClick={() => dispatch(decrement())}>
        - إنقاص 1
      </button>

      <hr />

      {/* 8) إدخال رقم لجمعه مع العداد */}
      <input
        type="number"
        value={amount}
        onChange={(e) => setAmount(Number(e.target.value))}
      />

      {/* 9) زر زيادة بقيمة مُعطاة */}
      <button onClick={() => dispatch(incrementByAmount(amount))}>
        زيادة بالقيمة المدخلة
      </button>
    </div>
  );
}

export default Counter;
JavaScript

شرح ما يحدث في الكمبوننت:

  1. قراءة القيمة من الـ stateconst count = useSelector((state) => state.counter.value);
    • state.counter → اسم الـ slice في الـ store.
    • .value → الحقل داخل initialState.
  2. تجهيز dispatch const dispatch = useDispatch();
  3. زر الجمع<button onClick={() => dispatch(increment())}>
    • يستدعي الـ action increment التي تعرّفناها في counterSlice.
    • هذه الـ action تصل للـ reducer وتزيد القيمة 1.
  4. زر الطرح<button onClick={() => dispatch(decrement())}>
    • تستدعي decrement وتنقص القيمة 1.
  5. زيادة بقيمة معيّنةdispatch(incrementByAmount(amount))
    • هنا نرسل قيمة إضافية مع الـ action اسمها payload.
    • في الـ reducer: incrementByAmount: (state, action) => { state.value = state.value + action.payload; }
🧩 ملخص تدفق العملية (Flow)
  1. المستخدم يضغط زر +:
    • الكمبوننت ينفذ: dispatch(increment())
  2. Redux يرسل الـ action إلى الـ reducer داخل counterSlice.
  3. دالة increment تعدّل state.value بزيادة 1.
  4. Redux ينشئ نسخة جديدة من الـ state ويُحدّثها في الـ store.
  5. بما أن قيمة state.counter.value تغيّرت:
    • useSelector في الكمبوننت تلاحظ التغيير
    • React تعيد رندر Counter
    • يظهر الرقم الجديد على الشاشة.