مفهوم redux و کلیت Redux-toolkit

مفهوم redux و کلیت Redux-toolkit

در این مقاله مفهوم redux و کلیت Redux-toolkit را مورد بررسی قرار میدهیم تا مفهوم کلی این موارد را متوجه شده و بتوانیم پروژه های ساده ای با آن پیاده سازی نماییم. در نظر داشته باشید استفاده از ریداکس اجباری نیست و بیشتر برای پروژه های با مقیاس بزرگ توصیه میشود.

کتابخانه Redux چیست؟

Redux یک کتابخانه جاواسکریپت است که برای ساخت رابط کاربری اپلیکیشن‌ها و صفحات وب استفاده می‌شود. این کتابخانه حالات (states) مختلف را در برنامه‌های جاوا اسکریپت مدیریت می‌کند.

ما برای استفاده از دیتا در کامپوننت ها از props استفاده میکنیم تا بتوانیم دیتا را به کامپوننت ها پاس بدهیم.

اما وقتی تعداد کامپوننت های تو در تو زیاد بشود، استفاده از این روش اصلا پیشنهاد نمیشه و به این شلوغ کاری به اصطلاح prop drilling گفته میشه.

به زبان ساده تر ، انتقال، اشتراک گزاری و تغییر داده در تمامی کامپوننت ها باعث شلوغی و بهم ریختگی می شود.Redux اینکار را با جداسازی لایه data از بقیه کدها آسانتر میکند.

ریداکس که در سال 2015 عرضه شده است. یک کتابخانه برای مدیریت کردن استیت های گلوبال هستش که در کتابخانه و فریمورک هایی مثل ری اکت و انگولار از آن استفاده میشود.

البته اگر پروژه بزرگ و پیچیده باشد مناسبتراست، در غیر اینصورت مواردی مانند context api کفایت میکند.

این کتابخانه با تمام وابستگی‌های خود (Dependency) تنها 2 کیلوبایت حجم دارد و با بکارگیری آن، لازم نیست نگران سنگین شدن پروژه خود باشید.

قبل از آن که در کدنویسی ریداکس ریز شویم، اجازه دهید مفهومی به نام فلاکس را بررسی کنیم.

Flux چیست؟

  • فلاکس یک مفهموم برنامه نویسی است که در آن جهت حرکت داده یک طرفه است.
    دیتا در این حالت وارد برنامه می شود و یک مسیر را طی می کند تا بر روی صفحه چاپ شود.
    Flux یک الگو برای مدیریت نحوه جریان یافتن داده ها در یک برنامه ری اکت است.
  • همان طور که دیدید، هنگام کار با کامپوننت های ری اکت گاهی نیاز بود داده ای را از یک کامپوننت والد به کامپوننت فرزند پاس دهیم. الگوی Flux از این مدل به عنوان روش پیش فرض برای مدیریت داده ها استفاده می کند .
    در معماری Flux سه بخش مجزا برای مدیریت داده ها وجود دارد:
  • Store
  • dispacher
  • View (کامپوننت های برنامه)

Flux
flux

Actions − اکشن ها به dispatcher ارسال می شوند تا جریان داده آغاز شود .
Dispatcher − این قسمت مغر اصلی توزیع داده است و داده را به Store منتقل میکند .
Store − استور جایی ست که استیت ها و منطق برنامه قرار دارد. هر استور یک استیت دارد که بر اساس نیاز قابل تغییر است .
View − این بخش اطلاعات را از Store دریافت می کند و بر روی صفحه چاپ می کند.

مهمترین فریم ورک های آماده در فلاکس :

  • Facebook Flux
  • alt
  • js-nuclear
  • uxibleFl
  • reflux
  • Fluxxor
  • react-flux
  • redux

Redux برای ذخیره داده‌ها در لایه نمایش مورد استفاده قرار می‌گیرد. کاربرد اصلی ریداکس در کنار فریم ورک هایی مانند React و React Native است.

اما می‌توان از آن در Angular، Angular2، Vue، Mithril و سایر کتابخانه‌های JS استفاده کرد چون ریداکس هیچ مشکلی در ادغام شدن با سایر فریم ورک‌ها ندارد. این فریم ورک با یک مکانیسم ساده به راحتی خودش را در کنار سایر زبان‌ها جا می‌دهد.

Store

جایی هست که تمامی داده ها نگه داری می شود.store یک اتصال راحت برای تعامل با state های global در اپلیکیشن فراهم می کند.

Actions

کارهایی که قرار است ما با Store انجام بدیم.هر زمانی که ما میخوایم قسمتی از state را تغییر بدهیم، این فرآیند با یک Action انجام میشه.اکشن فقط یک  JSON objects است.

dispatch

زمانی که ما بخواهیم یک action اجر شود، ما action رو dispatch می کنیم.

مفهوم redux و کلیت Redux-toolkit

قاعدتا از اینجا به بعد این مقاله مفهوم redux و کلیت Redux-toolkit ، انتظار کدنویسی در بستر ریداکس و آموزش آن را میکشید. اما باید به شما بگوییم که ریداکس سختی هایی به همراه دارد و شاید به مقدار این حجم کار، خروجی مناسبی به ما نمیدهد!

در ریداکس ما مجبوریم پکیجایی مثل redux-thunk, redux-persist , redux-saga و… نصب کنیم و همین امر باعث شلوغی بیش از حد کد میشود و مشکلات فراوانی به همراه خواهد داشت. در ضمن کانفیگ کردن آن هم میتواند سخت باشد.

خب… راه حل پس چیست؟

redux-toolkit آمده که همه ی این کارها را یکجا به ما بدهد و کار را راحت تر کند.

برای حل این مشکل، یکی از maintainerهای اصلی تیم ریداکس، یک پروژه را تحت عنوان Redux Toolkit، مدتها قبل برای حل مشکلات عنوان شده شروع کرده است و این پکیج، جدیداً به قالب رسمی create-react-app اضافه شده است.

که در واقع یک روش استاندارد و به اصطلاح opinionated برای ایجاد پروژه‌های ریداکسی می‌باشد و شامل تمامی وابستگی‌های موردنیاز برای کار با Redux از قبیل redux-thunk و همچنین Redux DevTools است. 

پس بهتر است به این نکته دقت کنید که redux-toolkit در واقع یه راه برای ساده تر نوشتن ریداکس میباشد و در پشت پرده دقیقا همون کارایی که با ریداکس میکردیم را انجام میدهد. منتها ما دیگر نیازی نیست با خیلی از این پیچیدگی هایی که ریداکس داشت سر و کله داشته باشیم.

شروع به کار و نصب ریداکس تولکیت

در ابتدا در CMD کامند زیر را بزنید تا با تمام پکیج های redux-thunk , rtk query و… نصب شود.

npx create-react-app myRedu –template redux
cd myRedu
npm start

این ساختار خیلی شبیه به قالب پیش‌فرض create-react-app می‌باشد. مهم است در ابتدای آموزش ساختار فایل ها و کدهای دیفالت ریداکس تولکیت را کامل بشناسیم. به همین منظور یک مرور کلی بروی مهمترین موارد این فایل ها خواهیم داشت.

پروژه‌ی ایجاد شده‌ی با قالب redux ، یک فایل با نام store و همچنین یک دایرکتوری را به نام features دارد. اگر به فایل store.js مراجعه کنید، خواهید دید که تنظیمات اولیه‌ی ایجاد store را در قالب یک مثال Counter ایجاد کرده‌است:


 import { configureStore } from ‘@reduxjs/toolkit’;
import counterReducer from ‘../features/counter/counterSlice’;
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});

در کد فوق، نحوه‌ی ایجاد store، نسبت به حالت معمول ریداکس ، آسان تر و خواناتر است. نکته‌ی جالب این است که به همراه کد فوق، Redux DevTools و همچنین redux-thunk هم از قبل تنظیم شده‌اند و در نتیجه، نیازی به تنظیم و نصب آنها نیست.

reducer در ریداکس یک تابع جاوا اسکریپتی است، این تابع دو پارامتر می‌گیرد: state فعلی و action.

ویژگی مهم دیگر در تولکیت ، پوشه‌ی features می‌باشد که یک روش رایج برای گروه‌بندی کامپوننت‌ها، همراه با فایل‌های وابسته‌ی آن‌ها است.

فایل های درون پوشه‌ی features عبارتست از:

Counter
Counter.module.css
counterSlice.js
counterAPI
counterSlice.spec

در بین اینها، مهمترین فایل counter.js است که در نهایت درون صفحه رندر خواهد شد.

درون این فایل با استفاده از Redux Hooks کار اتصال به store و همچنین dispatch کردن اکشن‌ها صورت میپذیرد.

کد صفحه counter.js را باز کنید. همانطور که میبینید، تعدادی button در خروجی کامپوننت اصلی صفحه قرار گرفته اند که با کلیک بروی هر کدام ، dispatch مربوطه از صفحه counterSlice فراخوانی میشود.

البته قبل از آن تمام این dispatch ها از counterSlice در صفحه counter فراخوانی شده اند.


 import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from ‘./counterSlice’;

در نظر داشته باشید، مقدار استیت اصلی در counter.js است.

  const [incrementAmount, setIncrementAmount] = useState(‘2’);

اما با توجه به اکشن های روی باتن های داخل صفحه، آپدیت مقدار با توجه به اکشن مربوطه در صفحه counterSlice انجام میپذیرد. در این قالب جدید، ترکیب این قطعات هستند که شیء اصلی یا در واقع همان state کلی پروژه را تشکیل خواهند داد. 

برای ایجاد یک قطعه جدید، از تابع createSlice استفاده شده است. این تابع، تعدادی پارامتر را از ورودی دریافت می‌کند:


 export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(incrementAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(incrementAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.value += action.payload;
      });
  },
});

  • name: برای هر بخش از state، می‌توانیم یک نام را تعیین کنیم و این همان عنوانی خواهد بود که می‌توانید توسط Redux DevTools مشاهده کنید.
  • initialValue: در اینجا می‌توانیم مقادیر اولیه‌ای را برای این بخش از state، تعیین کنیم که در مثال فوق، value به مقدار صفر تنظیم شده‌است.
  • reducers: این قسمت محل تعریف actionهایی هستند که قرار است state را تغییر دهند. نکته جالب توجه این است که state در هر کدام از متدهای فوق، به ظاهر mutate شده است؛ اما همانطور که به صورت کامنت نیز نوشته‌است، در پشت صحنه از کتابخانه‌ای با عنوان immer استفاده می‌کند که در عمل بجای تغییر state اصلی، یک کپی از state جدید را جایگزین state قبلی خواهد کرد.

در نهایت خروجی صفحه counterSlice به صفحه store در پوشه store منتقل میشود.

import counterReducer from ‘../features/counter/counterSlice’;

در نظر داشته باشید در نهایت خروجی counter.js در صفحه رندر خواهد شد و در APP.js فراخوانی میشود.

import { Counter } from ‘./features/counter/Counter’;

قطعا در جریان هستید که فایل  Counter.module.css در واقع استایل‌های مربوط به کامپوننت فوق میباشد که به صورت CSS module اضافه شده‌است. 

تعریف من از ریداکس : Redux is a global state

و اما در ادامه برای درک بهتر مفهوم ریداکس تولکیت…

ساخت پروژه todo list با استفاده از redux toolkit

من در این آموزش روی رابط کاربری تمرکز نمی کنم و بیشتر تمرکز بروی کدها میباشد.

حالا ما رابط کاربری داریم که به شکل زیر است:

تمرین با ریداکس

برای شروع از صفحه index.js کد زیر را درج میکنیم.

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// import "./index.css";
import store from "./app/store";
import { Provider } from "react-redux";
ReactDOM.render(
  <Provider store={store}>
      <App />
  </Provider>,
  document.getElementById("root")
);

همانطور که در کد بالا میبینید، مقدار Provider را از react-redux فراخوانی کرده و همچنین مقدار STORE را از پوشه خود فراخوانی میکنیم. در نهایت در ReactDOM.render به Provider ارزش یا مقدار استور را میدهیم.

بهتر است همین الان تکلیف کدهای استور را نیز معلوم کنیم. کد زیر را در استور خود قرار دهید:


 import { configureStore } from "@reduxjs/toolkit";
import todoReducer from "../features/todoSlice";
const store = configureStore({
  reducer: {
    todo: todoReducer,
  },
});
export default store;

STORE مؤلفه‌ای است که شما به ارائه‌دهنده redux ارائه می‌دهید که آن را از وجود وضعیت آگاه می‌کند.

این کد باعث می شود استور به reducer ما گوش دهد و به این ترتیب می توانیم به متغیرها و توابع داخل دسترسی پیدا کنیم. ما reducer خود را “todo” نامگذاری می کنیم.

اما بعد از این دو فایل، یکی از فایل های اصلی ما App.js میباشد. کد زیر را در آن وارد کنید:


import { useState } from "react";
import "./App.css";
import TodoItem from "./TodoItem";
import { useSelector, useDispatch } from "react-redux";
import { addTodo, removeTodo } from "./features/todoSlice";
function App() {
  const [input, setInput] = useState("");
  const count = useSelector((state) => state.todo.count);
  const todos = useSelector((state) => state.todo.todos);
  const dispatch = useDispatch();
  const handleAddTodo = (e) => {
    e.preventDefault();
    dispatch(addTodo(input));
  };
  const handleTodoDone = (id) => {
    dispatch(removeTodo(id));
  };
  return (
    <div className="App">
      <h1>TODO List</h1>
      <form className="App-form" onSubmit={handleAddTodo}>
        <input type="text" onInput={(e) => setInput(e.target.value)} />
        <button type="submit">+</button>
      </form>
      <div className="Todos">
        {count > 0 &amp;&amp;
          todos.map((todo) => (
            <TodoItem
              key={todo.id}
              text={todo.text}
              id={todo.id}
              onCheck={handleTodoDone}
            />
          ))}
        {count === 0 && <p>No todos</p>}
      </div>
    </div>
  );
}
export default App;

همانطور که در خط اول میبینید، useSelector و useDispatch برای استفاده از state که به تازگی در برنامه ایجاد شده است، ایمپورت میکنیم.

در تابع App، با استفاده از useSelector، هر دو استیت count و todo را دریافت کنید.(هر دو در فایل todoslice وجود دارند که در ادامه آن را خوهیم ساخت)

توجه داشته باشید که برای دسترسی به هر یک از این حالت ها، باید به نام reducer که در فایل store مشخص شده است دسترسی داشته باشید.

تابع dispatch را که برای اجرای توابع addTodo و removeTodo استفاده می شود، استخراج کنید.

تابع handleAddTodo را بنویسید که هنگام ارسال فرم فعال می شود.
فراخوانی ()e.preventDefault ضروری است زیرا اقدام پیش‌فرض فرم این است که صفحه را پس از ارسال به‌روزرسانی کند. اگر به آن اجازه دهید صفحه را رفرش کند، تمام وضعیت ما بازنشانی و صفر می شود.

برای تابع handleTodoDone، زمانی که کاربر روی یک مورد todo کلیک می‌کند، آن را فعال می‌کند. شناسه todo برای تشخیص اینکه کدام todo حذف شده است استفاده می شود.

ادامه کد هم که واضح است. در خروجی HTML از آرایه todos که در صفحه todoslice(ساخته خواهد شد) ایجاد و پر میشود، یک حلقه زده و مقادیر داخل آن را پر میکند و نشان میدهد. در صورت نداشتن مقدار، متن no todo منتشر میشود.

یک فایل TodoItem.js بسازید و کد زیر را در آن منتشر کنید:


 const TodoItem = (props) => {
  const deleteTodo = () => {
    props.onCheck(props.id);
  };
  return (
    <div onClick={deleteTodo}>
      <input type="checkbox"></input>
      <label>{props.text}</label>
    </div>
  );
};
export default TodoItem;

todoitem همان فایلی است که در App.js مستقیم فراخوانی کردیم و در قسمت return تابع اصلی آن، بر طبق ساختار اصلی آن، حلقه MAP زدیم و خروجیمان را تشکیل دادیم.

این فایل میگوید که به ازای هر مرتبه حلقه MAP یک دیویژن که در خود یک input و label دارد ایجاد کن و به event آن، مقدار deleteTodo را منتسب کن.

deleteTodo قرار است کل دیویژن را حذف نماید.

آخرین فایلمان، ToDoSlice.js است. آن را ایجاد کرده و مقدار کد زیر را در آن درج نمایید:


import { createSlice } from "@reduxjs/toolkit";
const initialState = {
  count: 0,
  todos: [],
};
export const todoSlice = createSlice({
  name: "todo",
  initialState,
  reducers: {
    addTodo: (state, action) => {
      const todo = {
        id: Math.random() * 100,
        text: action.payload,
      };
      state.todos.push(todo);
      state.count += 1;
    },
    removeTodo: (state, action) => {
      state.todos = state.todos.filter((todo) => todo.id !== action.payload);
      state.count -= 1;
    },
  },
});
export const { addTodo, removeTodo } = todoSlice.actions;
export default todoSlice.reducer;

برای ساختن اسلایس، باید createSlice را وارد کنید. که تابعی در داخل بسته Redux Toolkit است.

بعد، استیت اولیه خود را تعریف کنید. در این حالت، دو متغیر بخشی از استیت خواهند بود:

count: یک عدد صحیح که تعداد کارهای موجود در برنامه را ردیابی می کند (در ابتدا برابر با 0)
todos: لیستی که شامل تمام کارهایی است که کاربر اضافه کرده است (در ابتدا خالی)

اکنون از تابع createSlice که وارد شده است استفاده کنید. سه آرگومان ورودی می گیرد:

نام اسلایس (در این مورد todo)
استیت اولیه (تعریف شده در بالا)
reducer (توابعی که برای تغییر مقادیر state استفاده خواهند شد)

اما در ادامه…

addTodo ← با استفاده از متن ارسال شده در آرگومان یک آیتم todo جدید ایجاد می کند و یک شناسه تصادفی به آن اختصاص می دهد. این todo ایجاد شده را به لیست todos ، پوش (PUSH) میکند و مقدار شمارش را افزایش می دهد.

توجه داشته باشید که برای دریافت آرگومان به تابع، از action.payload استفاده می کنیم. اگر به بیش از یک آرگومان نیاز دارید، می توانید آرگومان ها را به عنوان یک شی ارسال کنید و با استفاده از payload به آن دسترسی داشته باشید.(action.payload.variable)

removeTodo ← مورد todo را با استفاده از شناسه داده شده در آرگومان ها از لیست کارها حذف می کند و مقدار شمارش را کاهش می دهد.

فراموش نکنید در نهایت، reducer را صادر کنید. این در STORE استفاده می شود تا بتوان state را به برنامه provide کرد.

دانلود کل پروژه

امیدواریم از این مقاله نهایت استفاده را برده باشید و آن را با دوستانتان به اشتراک بگذارید. تیم تولید محتوای مدرسه اینترنتی پرنیان این مقاله را تهیه کرده است.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *