آموزش استفاده از Redux و Redux Toolkit

در این مقاله قصد داریم تا Redux و Redux Toolkit، را که مجموعه‌ای از ابزارهایی است که استفاده از Redux را ساده‌تر می‌کند باهم بررسی کنیم.

Redux چیست؟

Redux یک کتابخانه مدیریت state است که به ما این امکان را می‌دهد تا state برنامه‌های جاوااسکریپتی خود را به طور کارآمدتر و قابل پیش‌بینی مدیریت کنیم.

تصور کنید که در حال ساختن یک خانه هستیم و باید تمام مصالحی که استفاده می‌کنیم و مقدار پولی که خرج می‌کنیم را پیگیری نماییم. به جای پیگیری همه این موارد در ذهن خود و یا روی یک تکه کاغذ، می‌توانیم از یک دفتر کل برای پیگیری هر معامله استفاده کنیم. Redux به طور مشابه با پیگیری state برنامه ما در یک مکان به نام store کار می‌کند.

فرض کنید در حال ساخت یک سایت e-commerce هستیم. ممکن است لازم باشد تا اقلام موجود در سبد خرید کاربر، اطلاعات پرداخت و جزئیات حمل و نقل آن‌ها را پیگیری کنیم.

Redux به‌جای این که این اطلاعات را با استفاده از props از کامپوننتی به کامپوننت دیگر انتقال دهد، به ما این امکان را می‌دهد تا آنها را در یک مکان مرکزی ذخیره نماییم، جایی که به راحتی قابل دسترس بوده و به‌روز باشد. این امر مدیریت stateهای پیچیده و سازماندهی برنامه را آسان‌تر می‌کند.

لازم است به این نکته توجه داشته باشیم که Redux به React محدود نمی‌شود و می‌توانیم از آن با فریمورک‌های دیگر یا حتی وانیلا جاوااسکریپت استفاده نماییم.

چرا باید از Redux استفاده کنیم؟

Redux می‌تواند به ساده‌سازی فرآیند مدیریت state کمک کند، به‌ویژه هنگامی که با کامپوننت‌های پیچیده و به هم پیوسته سروکار داریم. در ادامه دلایلی را ذکر می‌کنیم که ممکن است به آن دلیل بخواهیم از Redux در برنامه خود استفاده کنیم:

  • مدیریت state متمرکز: با استفاده از Redux، می‌توانیم state کل برنامه خود را در یک store ذخیره کنیم. همین کار، مدیریت و دسترسی به داده‌ها را در کامپوننت‌های مختلف آسان‌تر می‌کند.
  • به‌روزرسانی‌های state قابل پیش‌بینی: Redux جریان واضحی از داده‌ها دارد. به این معنی که تغییرات در state، تنها زمانی اتفاق می‌افتد که ما actionای را ایجاد کرده و از طریق Redux ارسال نماییم. این امر، درک اینکه چگونه داده‌های برنامه ما در پاسخ به اقدامات کاربر تغییر می‌کند را آسان‌تر می‌نماید.
  • دیباگ کردن آسان‌تر: با کمک Redux DevTools، ما یک رکورد واضح از تمام تغییراتی که در state برنامه اتفاق می‌افتد، داریم. همین موضوع، مکان‌یابی و رفع مشکلات را در کد ما آسان‌تر می‌کند. به این ترتیب، در زمان و تلاش ما برای فرآیند دیباگ کردن صرفه جویی می‌شود.
  • عملکرد بهتر: Redux با به حداقل رساندن تعداد به‌روزرسانی‌های state و کاهش نیاز به prop drilling، به بهبود عملکرد برنامه ما کمک می‌کند.

Redux چگونه کار می کند؟

همانطور که قبلا به آن اشاره کردیم، Redux ما را قادر می‌سازد تا یک store متمرکز داشته باشیم که state کل برنامه را مدیریت می‌کند. همه کامپوننت‌های برنامه ما می‌توانند به این store دسترسی داشته باشند و در صورت نیاز، داده‌ها را از آن بازیابی کرده یا به‌روز رسانی نمایند.

کامپوننت‌های کلیدی که این رویکرد متمرکز را در مدیریت state ممکن می‌سازند عبارتند از:

  1. Store
  2. Actions
  3. Dispatch
  4. Reducers

در ادامه نقش هر یک از این موارد را به صورت کامل بررسی می‌کنیم.

Store

Redux store مانند یک ظرف بسیار بزرگ است که تمام داده‌های برنامه ما را در خود نگه می‌دارد. store را به عنوان جعبه‌ای در نظر می‌گیریم که محفظه‌های مختلف برای انواع داده‌های مختلف دارد. می‌توانیم هر داده‌ای را که می‌خواهیم، با هر نوع داده‌ای مانند رشته‌ها، اعداد، آرایه‌ها، آبجکت‌ها و حتی توابع را در این محفظه‌ها ذخیره نماییم. همچنین، store تنها منبع برای state برنامه ما است. این بدان معناست که هر کامپوننت در برنامه می‌تواند برای بازیابی و به‌روزرسانی داده‌ها به آن دسترسی داشته باشد.

Actions

یک action آبجکتی است که توضیح می‌دهد چه تغییراتی باید در state برنامه ما ایجاد شود. action داده‌ها را از برنامه ما به Redux store ارسال می‌کند و به عنوان تنها راه برای به‌روزرسانی store عمل می‌کند.

یک action باید دارای یک ویژگی type باشد که action در حال انجام را توصیف کند. این ویژگی type معمولاً به عنوان یک constant رشته برای اطمینان از ثبات و جلوگیری از اشتباهات تایپی تعریف می‌شود.

همینطور یک action علاوه بر ویژگی type، می‌تواند ویژگی payload نیز داشته باشد. ویژگی payload داده‌هایی را نشان می‌دهد که اطلاعات اضافی در مورد action در حال اجرا ارائه می‌دهند.

برای مثال، اگر یک action از تایپ ADD_TASK باشد، payload ممکن است یک آبجکت حاوی id، text و completed status یک آیتم تسک جدید باشد. به عنوان مثال:

{
  type: 'ADD_TASK',
  payload: {
    id: 1,
    text: 'Buy groceries',
    completed: false
  }
}

باید به این نکته توجه داشته باشیم که برای ایجاد اکشن‌ها از Action Creatorها استفاده می‌کنیم. Action creatorها توابعی هستند که آبجکت‌های اکشن را ایجاد و return می‌کنند.

در ادامه مثالی برای یک action creator داریم که text یک تسک را می‌گیرد و یک آبجکت اکشن را برای اضافه کردن تسک به Redux store برمی‌گرداند:

function addTask(taskText) {
  return {
    type: 'ADD_TASK',
    payload: {
      id: 1,
      text: taskText,
      completed: false
    }
  }
}

Dispatch

Dispatch در redux تابعی است که توسط store ارائه می‌شود و به ما این امکان را می‌دهد تا برای به‌روزرسانی state برنامه خود یک action ارسال کنیم. هنگامی که ما dispatch را فراخوانی می‌کنیم، store از طریق همه reducerهای موجود actionای را انجام می‌دهد، که به نوبه خود state را مطابق با آن به‌روزرسانی می‌کند.

Reducers

Reducer در Redux تابعی است که state فعلی یک برنامه و یک action را به عنوان آرگومان می‌گیرد و یک state جدید را بر اساس actionای که دریافت کرده است return می‌کند. به عنوان مثال:

const initialState = {
  count: 0
};

function counterReducer(state = initialState, action) {
  switch(action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
}

در کد بالا یک reducer ساده به نام counterReducer داریم که state متغیر count را مدیریت می‌کند. این reducer دو آرگومان state و action را دریافت می‌کند. آرگومان state وضعیت فعلی برنامه ما را نشان می‌دهد، و آرگومان action بیانگر اقدامی است که برای تغییر state ارسال شده است.

reducer از یک دستور switch برای بررسی type اکشن استفاده می‌کند و بر اساس آن تایپ، state را متناسب با آن به‌روزرسانی می‌کند.

به عنوان مثال، اگر تایپ اکشن INCREMENT باشد، reducer یک آبجکت state جدید که count یک واحد افزایش پیدا کرده است را return می‌کند. همچنین، اگر تایپ اکشن DECREMENT باشد، reducer یک آبجکت state جدید که count یک واحد کاهش پیدا کرده است را return خواهد کرد.

پیاده‌سازی یک برنامه واقعی

اکنون که با اصول اولیه Redux و نحوه عملکرد آن آشنا شدیم، قصد داریم تا یک پروژه ساده در دنیای واقعی را پیاده‌سازی کنیم. برای این مثال، ما یک برنامه ToDo List می‌سازیم که در آن می‌توانیم تسک‌ها را اضافه و حذف نماییم.

گام ۱: راه اندازی پروژه

با اجرای دستور زیر در ترمینال، یک پروژه React جدید ایجاد می‌کنیم.

npm create vite@latest your-project-name -- --template react

cd your-project-name

npm install

برای این کار می‌توانیم از ابزاری مانند Vite استفاده کنیم که یک پروژه React جدید ایجاد کرده و تمام وابستگی‌های لازم را نصب می‌کند.

گام ۲: نصب Redux

Redux برای عملیات خود به چند dependency نیاز دارد که عبارتند از:

  • Redux: کتابخانه هسته معماری redux را فعال می‌کند.
  • React Redux: اتصال کامپوننت‌های React به Redux store را ساده می‌کند.
  • Redux Thunk: این امکان را به ما می‌دهد تا بتوانیم منطق asynchronous را در اکشن‌های Redux خود بنویسیم.
  • افزونه Redux DevTools: برنامه Redux ما را به Redux DevTools متصل می‌کند.

می‌توانیم موارد ذکر شده را با استفاده از npm و به صورت زیر نصب کنیم:

npm install \

redux \

react-redux \

redux-thunk \

redux-devtools-extension

گام ۳: تنظیم reducerها

اکنون reducer را برای برنامه خود ایجاد می‌کنیم.

در دایرکتوری src یک فولدر جدید به نام reducers ایجاد کرده و در داخل آن دو فایل جدید به نام‌های index.js و taskReducer.js می‌سازیم.

فایل index.js نشان دهنده root reducer است که همه reducerهای منفرد را در برنامه ترکیب می‌کند. اما در مقابل، فایل taskReducer.js یکی از reducerهای منفرد است که در root reducer ترکیب می‌شود.

import taskReducer from "./taskReducer";
import { combineReducers } from "redux";

const rootReducer = combineReducers({
  tasks: taskReducer,
});

export default rootReducer;

در فایل index.js بالا، ما از تابع combineReducers برای ترکیب همه reducerها در یک root reducer استفاده می‌کنیم. در این مثال، ما فقط یک reducer(taskReducer) داریم، بنابراین آن را به عنوان آرگومان به combineReducers ارسال می‌نماییم.

سپس reducer ترکیبی حاصل export می‌شود تا سایر فایل‌های برنامه بتوانند آن را import کرده و برای ایجاد store از آن بهره‌مند شوند. در ادامه کد taskReducer را داریم:

const initialState = {
  tasks: []
};

const taskReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'ADD_TASK':
      return {
        ...state,
        tasks: [...state.tasks, action.payload]
      };
    case 'DELETE_TASK':
      return {
        ...state,
        tasks: state.tasks.filter(task => task.id !== action.payload)
      };
    default:
      return state;
  }
};

export default taskReducer ;

در فایل taskReducer.js بالا، یک تابع reducer تعریف می‌کنیم که دو آرگومان state و action را می‌گیرد. آرگومان state نشان دهنده state فعلی برنامه است، و آرگومان action نشان دهنده actionای است که برای به‌روزرسانی state ارسال می‌شود.

دستور switch در داخل reducer موارد مختلف را بر اساس type اکشن کنترل می‌کند. برای مثال، اگر تایپ اکشن ADD_TASK باشد، reducer یک آبجکت state جدید با یک تسک جدید را به آرایه tasks اضافه می‌کند. و اگر تایپ اکشن DELETE_TASK باشد، reducer یک آبجکت state جدید با تسک‌های فعلی فیلتر شده برای حذف تسک با id مشخص شده return می‌کند.

گام ۴: ایجاد Redux store

اکنون که تنظیمات اولیه برنامه خود را آماده کرده‌ایم، می‌خواهیم یک فایل جدید به نام store.js در دایرکتوری src می‌سازیم. اینجا جایی است که ما Redux store خود را تعریف می‌کنیم:

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { composeWithDevTools } from "redux-devtools-extension";

import rootReducer from "./reducers/index";

const store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk))
);

export default store;

کد بالا با ایجاد یک نمونه جدید از store با استفاده از تابع createStore، یک Redux store را راه‌اندازی می‌کند. سپس rootReducer، که تمام reducerهای برنامه را در یک reducer واحد ترکیب می‌کند، به عنوان آرگومان به createStore ارسال می‌شود.

علاوه بر این، کد از دو کتابخانه دیگر به نام‌های redux-thunk و edux-devtools-extension نیز استفاده می‌کند.

کتابخانه redux-thunk به ما این امکان را می‌دهد تا اکشن‌های asynchronous بنویسیم، در حالی که کتابخانه redux-devtools-extension به ما کمک می‌کند تا از افزونه مرورگر Redux DevTools برای دیباگ کردم و بازرسی state و actionهای موجود در store استفاده نماییم.

در نهایت، store را export می‌کنیم تا بتوانیم از آن در برنامه خود استفاده کنیم. ما از تابع composeWithDevTools برای ارتقای store با قابلیت استفاده از افزونه Redux DevTools و از تابع applyMiddleware برای اعمال middleware thunk در store استفاده می‌کنیم.

گام ۵: اتصال Redux Store به برنامه

برای اتصال Redux store به برنامه ToDo، باید از کامپوننت Provider از کتابخانه react-redux استفاده نماییم.

ابتدا تابع Provider و Redux store که ایجاد کردیم را به فایل main.jsx خود import می‌نماییم. سپس، کامپوننت App را داخل تابع Provider قرار داده و store را به عنوان prop به آن پاس می‌دهیم. این باعث می‌شود تا Redux store در دسترس همه کامپوننت‌های داخلی App قرار بگیرد.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";

import { Provider } from "react-redux";
import store from "./store";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

گام ۶: استفاده از Redux DevTools

هنگامی که Redux <Provider> را در برنامه خود راه‌اندازی کردیم، می‌توانیم از افزونه Redux DevTools بهره‌مند شویم. برای شروع کار با آن، ابتدا باید افزونه Redux DevTools را برای مرورگر خود دانلود کنیم.

پس از نصب، DevTools یک تب جدید مخصوص Redux به قسمت Developer Tools مرورگر ما اضافه می‌کند.

با کلیک بر روی تب State در Redux DevTools، می‌توانیم state کل Redux store و هر actionای که ارسال شده است همراه با payloadهای آن‌ها را مشاهده کنیم.

این موضوع می‌تواند در هنگام دیباگ کردن برنامه بسیار مفید باشد، زیرا می‌توانیم state و actionها را به صورت real-time بررسی نماییم.

گام ۷: تنظیم Redux Actions

اکنون قصد داریم تا actionهای خود را ایجاد کنیم. همانطور که قبلا به آن اشاره کردیم، actionها نشان دهنده چیزی هستند که در برنامه اتفاق افتاده است. به عنوان مثال، هنگامی که کاربر یک تسک جدید اضافه می‌کند، یک اکشن add task را راه‌اندازی می‌نماید. به طور مشابه، هنگامی که یک تسک را حذف می کند، یک اکشن delete task را آغاز می‌کند.

برای ایجاد actionها، یک فولدر جدید به نام actions در دایرکتوری src ایجاد می‌کنیم و سپس یک فایل جدید به نام index.js در آن می‌سازیم. این فایل شامل تمام action creatorها برای برنامه ما خواهد بود.

export const addTodo = (text) => {
  return {
    type: "ADD_TASK",
    payload: {
      id: new Date().getTime(),
      text: text,
    },
  };
};

export const deleteTodo = (id) => {
  return {
    type: "DELETE_TASK",
    payload: id,
  };
};

کد بالا دو action creator را export می‌کند: addTodo و deleteTodo. این توابع یک آبجکت با ویژگی type را return می‌کنند که actionای که رخ داده است را توصیف می‌نماید.

در مورد addTodo، ویژگی type روی "ADD_TASK" تنظیم می‌شود که نشان می‌دهد یک تسک جدید اضافه شده است. ویژگی payload حاوی یک آبجکت است که مقادیر id و text تسک جدید را دربر می‌گیرد. id با استفاده از متد جدید new Date().getTime() تولید می‌شود و یک شناسه منحصربه‌فرد بر اساس زمان فعلی ایجاد می‌کند.

در مورد deleteTodo، ویژگی type روی "DELETE_TASK" تنظیم می‌شود که نشان می‌دهد یک تسک حذف شده است. ویژگی payload حاوی id تسکی است که باید حذف شود.

می‌توانیم این action creatorها را با استفاده از متد dispatch() به Redux store بفرستیم، که تابع reducer مربوطه را فعال کند تا state برنامه را متناسب با آن، به‌روزرسانی نماید.

گام ۸: ارسال actionها

اکنون که actionهای لازم را ایجاد کرده‌ایم، می‌توانیم به سمت ایجاد کامپوننت‌هایی برویم که این actionها را ارسال می‌کنند.

یک فولدر جدید به نام components در دایرکتوری src ایجاد می‌کنیم. در داخل این فولدر هم دو فایل جدید با نام‌های Task.jsx و TaskList.jsx می‌سازیم.

کامپوننت Task.jsx مسئول افزودن تسک‌ها خواهد بود. اما قبل از ادامه، باید موارد زیر را در فایل خود import کنیم:

  • اکشن addTodo: برای افزودن تسک‌های جدید به state.
  • هوکuseDispatch: برای ارسال اکشن addTodo.
  • useRef: برای این که بتوانیم به المنت‌های HTML رفرنس دهیم.
import { useRef } from "react";
import { useDispatch } from "react-redux";
import { addTodo } from "../actions";

پس از import کردن این کامپوننت‌های ضروری، می‌توانیم به نوشتن کد برای Task.jsx ادامه دهیم.

const Task = () => {
  const dispatch = useDispatch();
  const inputRef = useRef(null);

  function addNewTask() {
    const task = inputRef.current.value.trim();
    if (task !== "") {
      dispatch(addTodo(task));
      inputRef.current.value = "";
    }
  }

  return (
    <div className="task-component">
      <div className="add-task">
        <input
          type="text"
          placeholder="Add task here..."
          ref={inputRef}
          className="taskInput"
        />
        <button onClick={addNewTask}>Add task</button>
      </div>
    </div>
  );
};

export default Task;

در کد بالا یک کامپوننت متشکل از یک فیلد ورودی و یک دکمه ایجاد کردیم. هنگامی که کاربر بر روی دکمه Add task کلیک می‌کند، تابع addNewTask اجرا می‌شود. این تابع از هوک useRef برای بدست آوردن مقدار فیلد ورودی استفاده می‌کند و هر گونه space اضافی قبل یا بعد را حذف می‌نماید. سپس اکشن addTodo را با تسک جدید به عنوان payload ارسال می‌کند.

اکنون به کامپوننت TaskList.jsx می‌رویم، که مسئول ارائه لیست تسک‌ها و مدیریت حذف آن‌ها می‌باشد. انجام این کار، باید موارد زیر را import کنیم:

  • هوک useSelector، که دسترسی به state را از Redux store فراهم می‌کند.
  • اکشن deleteTodo، که مسئول حذف یک تسک از لیست تسک‌ها در Redux store می‌باشد.
import { useSelector, useDispatch } from "react-redux";
import { deleteTodo } from "../actions";

اکنون کدی را برای TaskList.jsx می‌نویسیم که روی آرایه tasks map انجام می‌دهد و هر تسک را رندر می‌کند:

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { deleteTodo } from '../actions';

const TaskList = () => {
  const tasks = useSelector((state) => state.tasks);
  const dispatch = useDispatch();

  const handleDelete = (id) => {
    dispatch(deleteTodo(id));
  };

  return (
    <div className="tasklist">
      <div className="display-tasks">
        <h3>Your tasks:</h3>
        <ul className="tasks">
          {tasks.map((task) => (
            <li className="task" key={task.id}>
              {task.text}
              <button
                className="delete-btn"
                onClick={() => handleDelete(task.id)}
              >
                delete
              </button>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default TaskList;

در کدی که داریم، کامپوننت بر روی هر تسک در آرایه tasks یک loop ایجاد کرده و متن و یک دکمه delete را نمایش می‌دهد. هنگامی که کاربر روی دکمه delete کلیک می‌کند، تابع handleDelete فراخوانی می‌شود و اکشن deleteTodo را با id تسک به عنوان payload ارسال می‌نماید.

در نهایت کامپوننت‌ها را در فایل App.jsx خود import کرده و رندر می‌کنیم.

import Task from "./components/Task";
import TaskList from "./components/TaskList";

function App() {
  return (
    <div className="App">
      <Task />
      <TaskList />
    </div>
  );
}

export default App;

گام ۹: استایل‌دهی

از آن جایی که تمرکز اصلی ما در این مقاله بر روی عملکر برنامه است از این رو یک استایل ساده و اولیه برای زیباتر شدن برنامه در این لینک قرار داده‌ایم که می‌توانیم محتویات آن را در فایل index.css خود قرار دهیم.

نتیجه نهایی

پس از اجرای همه چیز، می‌توانیم نتیجه نهایی برنامه ToDo List خود را مشاهده کنیم.

در این برنامه می‌توانیم عنوان تسک را در قسمت ورودی وارد کرده و بر روی دکمه Add task کلیک کنیم و به این ترتیب تسک‌ها را اضافه نماییم. همچنین می‌توانیم با کلیک روی دکمه delete که در کنار هر تسک قرار دارد، تسک مورد نظرمان را حذف کنیم.

با استفاده از Redux DevTools می‌توانیم state و actionهای برنامه را نیز به راحتی ردیابی و بازرسی کنیم. این ویژگی به دیباگ کردن و درک نحوه عملکرد برنامه در پس‌زمینه کمک می‌کند.

اکنون ما یک برنامه ToDo کاملاً کاربردی داریم که آن را توسط Redux طراحی کرده‌ایم. سورس کد برنامه در GitHub موجود می‌باشد.

در نهایت، مهم است که به این موضوع توجه داشته باشیم که state یک برنامه هنگام استفاده از Redux در memory ذخیره می‌شود. بنابراین، اگر کاربر صفحه را رفرش کند یا برنامه را ببندد، state از بین می‌رود. بنابراین، برای این که بتوانیم اطلاعات برنامه را حتی پس از خروج یا بستن صفحه توسط کاربر حفظ کنیم، باید آن‌ها را در جایی خارج از حافظه برنامه ذخیره نماییم. برای انجام این کار می‌توانیم از تکنیک‌های مختلفی مانند local storage یا server-side storage استفاده کنیم.

در بخش بعدی مقاله Redux Toolkit را بررسی خواهیم کرد.

چگونه باید از Redux Toolkit استفاده کنیم؟

نوشتن کد Redux و مدیریت قسمت‌های مختلف برنامه، مخصوصا زمانی که مقیاس برنامه بزرگ‌تر شده و تعداد reducerها و actionها بیشتر می‌شود، می‌تواند کمی پیچیده باشد.

خوشبختانه Redux Toolkit راه حلی برای این مشکل ارائه می‌دهد و با حذف برخی از جنبه‌های پیچیده‌تر و تکراری‌تر Redux، مانند ایجاد reducerها و actionها، روشی ساده‌تر و کارآمدتر برای مدیریت state برنامه ارائه می‌کند.

مزایای Redux Toolkit

Redux Toolkit چندین مزیت نسبت به Redux دارد که عبارتند از:

  • راه‌اندازی آن آسان‌تر است و به dependencyهای کم‌تری نیاز دارد.
  • کد boilerplate را با اجازه دادن به ایجاد یک فایل منفرد به نام slice که actionها و reducerها را ترکیب می‌کند، کاهش می‌دهد.
  • پیش‌فرض‌های معقولی را برای ویژگی‌های معمولی مانند Redux Thunk و Redux DevTools ارائه می‌کند. این بدان معناست که ما دیگر نیازی به صرف زمان برای پیکربندی این ویژگی‌ها نداریم، زیرا آن‌ها قبلاً در Redux Toolkit ساخته شده‌اند.
  • Redux Toolkit در پس‌زمینه از کتابخانه immer استفاده می‌نماید، که mutation مستقیم state را فعال می‌کند و نیاز به کپی دستی state {...state} با هر reducer را از بین می‌برد.

در بخش‌های بعدی، نحوه استفاده از Redux Toolkit برای ساده‌سازی کد Reduxای که برنامه ToDo خود را با آن ساختیم، بررسی خواهیم کرد.

راه اندازی Redux Toolkit

برای استفاده از Redux Toolkit در برنامه React خود، باید دو dependency با نام‌های @reduxjs/toolkit و react-redux را نصب کنیم.

پکیج @reduxjs/toolkit ابزارهای لازم برای ساده‌سازی توسعه Redux را فراهم می‌کند، و react-redux برای اتصال Redux store به کامپوننت‌های React مورد استفاده قرار می‌گیرد.

npm install @reduxjs/toolkit react-redux

روش ایجاد یک slice

هنگامی که dependencyهای مورد نیاز را نصب کردیم، با استفاده از تابع createSlice یک slice جدید ایجاد می‌کنیم. slice بخشی از Redux store است که مسئول مدیریت یک بخش خاص از state می‌باشد.

اگر Redux store را به عنوان یک کیک در نظر بگیریم، هر slice نشان دهنده یک بخش خاص از داده در store است. با ایجاد یک slice، می‌توانیم رفتار state را در پاسخ به actionهای خاص با استفاده از توابع reducer تعریف کنیم.

برای ایجاد یک slice برای مدیریت برنامه ToDo، یک فایل جدید در مسیر src/features/todo/todoSlice.js ایجاد می‌کنیم و کد زیر را درون آن قرار می‌دهیم:

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  tasks: [],
};

const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {
    addTodo: (state, action) => {
      state.tasks.push({ id: Date.now(), text: action.payload });
    },
    deleteTodo: (state, action) => {
      state.tasks = state.tasks.filter((task) => task.id !== action.payload);
    },
  },
});

export const { addTodo, deleteTodo } = todoSlice.actions;

export default todoSlice.reducer;

کد بالا یک slice به نام todoSlice را با یک آبجکت initialState که حاوی یک آرایه خالی از تسک‌ها است، تعریف می‌کند.

آبجکت reducers دو تابع reducer را تعریف می‌کند که عبارتند از: addTask و deleteTask. تابع addTask یک آبجکت تسک جدید را به آرایه tasks اضافه می‌نماید، و تابع deleteTask یک تسک را بر اساس ویژگی id آن از آرایه tasks حذف می‌کند.

تابع createSlice به طور خودکار بر اساس نام توابع reducerای که ارائه می‌دهیم، action creatorها و action typeها را تولید می‌کند. بنابراین لازم نیست ما به صورت دستی action creatorها را تعریف کنیم.

export statement سپس action creatorهای تولید شده را export می‌کند، که می‌توانند در قسمت‌های دیگر برنامه برای ارسال actionها به slice استفاده شوند.

و در نهایت، تابع todoSlice.reducer تمام اکشن‌هایی را که به طور خودکار بر اساس آبجکت‌های reducer ارائه شده به تابع createSlice تولید می‌شوند، مدیریت می‌کند. با export کردن آن به عنوان مقدار پیش‌فرض، می‌توانیم آن را با reducerهای دیگر در برنامه ترکیب کنیم تا یک Redux store کامل ایجاد نماییم.

راه‌اندازی Redux Store

ایجاد یک Redux store با استفاده از Redux Toolkit بسیار ساده است.

ابتدایی‌ترین راه برای ایجاد یک store، استفاده از تابع configureStore() است که به طور خودکار تمام reducerهای تعریف شده در برنامه را با هم ترکیب می‌کند تا یک root reducer برای ما ایجاد نماید.

برای این که در برنامه‌ای که داشتیم store ایجاد کنیم، فایلی به نام src/store.js را اضافه کرده و کد زیر را به آن می‌افزاییم:

import { configureStore } from "@reduxjs/toolkit";
import todoReducer from "./features/todo/todoSlice";

const store = configureStore({
  reducer: {
    todo: todoReducer,
  },
});

export default store;

در این مثال، ابتدا تابع configureStore را از پکیج @reduxjs/toolkit و تابع todoReducer را از یک فایل جداگانه import می‌کنیم.

سپس، با فراخوانی configureStore و ارسال یک آبجکت با ویژگی reducer، یک آبجکت store می‌سازیم. ویژگی reducer آبجکتی است که نام sliceهای reducer را به توابع reducer مربوطه نگاشت می‌کند. در این مثال، یک reducer slice به نام todo داریم و تابع reducer متناظر آن todoReducer می‌باشد.

در نهایت آبجکت store را export می‌کنیم تا بتوانیم آن را import کرد و در قسمت‌های دیگر برنامه مورد استفاده قرار دهیم.

ارائه Redux store به React

برای اینکه store Redux را در دسترس کامپوننت‌های React در برنامه خود قرار دهیم، کامپوننت Provider را از کتابخانه react-reduximport می‌کنیم و کامپوننت root، که معمولاً <App> می‌باشد را درون آن قرار می‌دهیم.

کامپوننت Provider از store به‌عنوان یک prop استفاده می‌کند و آن را به تمام کامپوننت‌های childای که نیاز به دسترسی به آن دارند، منتقل می‌کند.

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";

import store from "./store.js";
import { Provider } from "react-redux";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);

ساخت کامپوننت‌ها

اکنون می‌توانیم کامپوننت‌های React مانند Task.jsx و TaskList.jsx را ایجاد کنیم که از هوک useSelector برای دسترسی به state فعلی از store استفاده می کنند. به طور مشابه، می‌توانیم از هوک useDispatch برای ارسال actionها برای به‌روزرسانی store استفاده کنیم، درست همانطور که در Redux ساده انجام دادیم.

جمع‌بندی

در این مقاله سعی کردیم تا با مفاهیم اصلی Redux و Redux toolkit آشنا شویم و به کمک آن‌ها، یک پروژه ساده و واقعی انجام دهیم.

دیدگاه‌ها:

حسین

اردیبهشت 5, 1403  در  10:42 ق.ظ

سلام
دوتا اشتباه توی کدها بود. اولی توی فایل taskReducer.js اشتباها export default rootReducer نوشته شده.
دومی هم توی فایل store باید rootReducer ایمپورت شود که اشتباهی taskReducer نوشته شده. البته در توضیحات درست اشاره شده ولی کد مشکل داره

سودا مجتهدی

اردیبهشت 5, 1403  در  12:45 ب.ظ

سلام
مرسی از توجهتون، اصلاح شد.

افزودن دیدگاه جدید