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