در این پست به نحوه استفاده از توابع ریداکس و middleware ها میپردازیم.
اول اینکه بخش اول این سری پستهای ریداکس را از این لینک مشاهده کنید. در این پست به توابع و نحوه استفاده از آنها در ریداکس میپردازیم.
برای نصب redux در پروژه خود، دستور زیر را در ترمینال وارد میکنیم :
npm i redux
وارد ویرایشگر میشویم و شروع میکنیم به کدنویسی...
برای ایجاد store ابتدا تابع createStore را از پکیج redux به فایل خود import میکنیم :
import { createStore } from 'redux';
میتوانیم بجای createStore از تابع legacy_createStore استفاده کنیم :
import { legacy_createStore as createStore } from 'redux';
این تابع دو ورودی دریافت میکند.
const store = createStore( reducer, initialState, );
ورودی اول همان reducer و ورودی دوم initialState یا حالت اولیه برنامه ماست. مثلا برای برنامه یک todolist میتوانیم این حالت را داشته باشیم:
const initialState = { list:[], //list of to do list }
برای شروع یک لیست خالی از to do list ها داریم.برای ایجاد تابع reducer هم ابتدا باید اکشن های خود را مشخص کنیم:
برای هرکدام از اکشن ها یک type خاص با حروف بزرگ تعریف میکنیم :
const ADD_TO_LIST = 'ADD_TO_LIST'; const REMOVE_FROM_LIST = 'REMOVE_FROM_LIST';
اکشنها یک آبجکت با type اجباری و payload اختیاری هستند. برای اضافه کردن به todolist خود، باید برای reducer یک action با type ثابت ADD_TO_LIST و payload هم که اطلاعات todo جدید را دارد، بفرستیم :
function addToList(id, todoTitle,todoText ){ return { type:ADD_TO_LIST , payload:{ id, todoTitle, todoText, } }; }
برای حذف از لیست هم باید برای reducer ثابت REMOVE_FROM_LIST به همراه id آن todo را ارسال کنیم.
function removeFromList(id ){ return { type:REMOVE_FROM_LIST , payload:{ id } }; }
پس از مشخص شدن اکشنها به سراغ ایجاد تابع reducer میرویم. این تابع دو ورودی دریافت میکند. ورودی اول state قبلی و ورودی دوم اکشن ارسالی در dispatch . اکشنها همواره مقدار type را دارند پس باید بر اساس type ارسالی نوع عملیات روی state را مشخص کنیم:
function toDoListReducer( state={initialState},action ){ switch(action.type){ case ADD_TO_LIST: const list = state.list; list.push(action.payload); return {...state,list} case REMOVE_FROM_LIST: const list = state.list.filter(id=>id!==action.payload.id); return {...state,list} default: return state; } }
پس از ایجاد reducer و initialState استور خود را ایجاد میکنیم:
import { legacy_createStore as createStore } from "redux" const initialState = { list:[], //list of to do list } const ADD_TO_LIST = 'ADD_TO_LIST'; const REMOVE_FROM_LIST = 'REMOVE_FROM_LIST '; function addToList(id, todoTitle,todoText ){ return { type:ADD_TO_LIST , payload:{ id, todoTitle, todoText, } }; } function removeFromList(id ){ return { type:REMOVE_FROM_LIST , payload:{ id } }; } function toDoListReducer( state={initialState},action ){ switch(action.type){ case ADD_TO_LIST: const list = state.list; list.push(action.payload); return {...state,list} case REMOVE_FROM_LIST: const list = state.list.filter(id=>id!==action.payload.id); return {...state,list} default: return state; } } const create = createStore( toDoListReducer , //reducer initialState, // initial state );
اکنون میتوانیم از store خود بخوانیم یا به آن اضافه کنیم. برای افزودن به store باید از تابع dispatch استفاده کنیم.
store.dispatch( addToList(1, 'todo 1','todo text 1' ) ); store.dispatch( {type:ADD_TO_LIST, payload:{ 2, 'todo 2','todo text 2' }} );
این دو خط هر کدام یک اکشن افزودن به لیست به reducer ارسال میکنند. در نتیجه 2 مورد اکنون به لیست ما در store افزوده شده است.
تابع dispatch اکشن را برای reducer ارسال میکند.
برای مشاهده لیست موجود در state هم از تابع subscribe استفاده میکنیم :
store.subscribe(()=>{ const state = store.getState(); console.log(state.list); });
با هر بار تغییر state در store تابعی که به subscribe پاس داده ایم اجرا میشود. و لیست ما در کنسول چاپ میشود.
تابع getState : هر وقت این تابع صدا زده شود مقدار فعلی state را میدهد.
تابع dispatch یک اکشن را برای reducer ارسال میکند. اما بین مبدا تا مقصد این اکشن، یک نفر نشسته و همواره اکشن را بررسی و سپس اجازه ارسال آن به reducer را میدهد. به این شخص میگوییم middleware!
در حقیقت middleware هم یک تابع است که قبل از reducer قرار میگیرد. یعنی وقتی dispatch اکشن را میفرستد ابتدا middleware آنرا بررسی میکند یا تغییر میدهد و سپس آنرا برای reducer میفرستد.
به نمونه کد middleware دقت کنید:
const logger = store=>next=>action=>{ console.log('before reduce :', store.getState() ); next(action); console.log('next reduce :', store.getState() ); }
این تابع store و action را میگیرد. در خط اول مقدار state قبل از اجرای reducer در کنسول چاپ میشود.
تابع next : اکشن ورودی را برای reducer میفرستد.
و خط سوم مقدار state بعد از تغییر state درون reducer را چاپ میکند.
برای store خود می توانیم بی نهایت middleware تعریف کنیم. middleware بالا تنها کاری که میکرد چاپ کردن مقدار state، قبل و بعد از تغییر آن است. میخواهیم یک middleware تعریف کنیم که حروف اول title در todoList را قبل از اعمال در reducer به حروف بزرگ تبدیل میکند:
const captlizer = store=>next=>action=>{ if(actio.type === ADD_TO_LIST){ let title = action.payload.todoTitle; let captlizeTitle = title[0].toUpperCase(); action.payload.todoTitle = captlizeTitle; } next(action); }
این middleware موقعی که action در تابع dispatch ارسال شود. ابتدا action را میگیرد، اگر type اکشن افزودن به لیست بود، حرف اول عنوان todo را به حروف بزرگ تبدیل میکند و سپس اکشن تغییر یافته را به وسیله تابع next به reducer ارسال میکند.
ما تعداد زیادی middleware میتوانیم تعریف کنیم. اینجا دو middleware مشخص کردیم، یکی captlizer و دیگری logger! برای هر کدام هم نقشی مشخص کردیم. اکنون چگونه middleware های خود را به store معرفی کنیم؟
در حقیقت تابع createStore میتواند سه ورودی هم دریافت کند. ورودی سوم middleware های ماست که البته اختیاری است. پس کد خود را به صورت زیر تغییر میدهیم.
const store = createStore( reducer, //reducer initialState, //initial state applyMiddleware(logger, captlizer), // middleware );
تابع applyMiddleware : این تابع از redux به فایل ما import میشود. این تابع middleware های ما را دریافت میکند و برای تحویل به store آماده میکند. ما بینهایت middleware میتوانیم آماده کنیم و به store معرفی کنیم.
در یک پروژه بزرگ، ما تعداد زیادی action داریم. وقتی تعداد اکشنها زیاد شود به تبع آن حجم تابع reducer هم زیاد میشود. مثلا 200 اکشن داریم برای اینکه از این 200 اکشن درون یک reducer استفاده کنیم به یک reducer خیلی خیلی بزرگ نیاز خواهیم داشت. برای حل این موضوع هم فکری کردهاند. ما میتوانیم تعداد زیادی reducer تعریف کنیم و سپس آنها را به هم بچسپانیم. دقت کنید همچنان مقدار type برای اکشن باید یکتا باشد.
تابع combineReducers : چند reducer را با هم می آمیزاند و یک reducer واحد برای معرفی به store ایجاد میکند.
دقت کنید هر store فقط یک reducer دارد. با این تابع، چند reducer را به یک reducer تبدیل میکنیم.
برای استفاده فقط کافیست :
import { combineReducers } from 'redux'; function reducer1(state,action){ ... } function reducer2(state,action){ ... } ... const reducer = combineReducers({ reducer1, reducer2, ... });
در ورودی این تابع هر تعداد reducer میتوان پاس داد.
امیدوارم پست مفید واقع شده باشد.