در قسمت قبل توانستیم با موفقیت از ActionCreator ها استفاده کنیم و حالا نوبت به کدنویسی نامتقارن است. برای کدنویسی نامتقارن به یک middleware خاص نیاز داریم که قبلا آماده شده است. پکیجی به نام redux-thunk در گیت هاب:
https://github.com/reduxjs/redux-thunk
همانطور که گفتم این کتابخانه یک middleware محسوب می شود که به ما اجازه می دهد کدهای نامتقارن بنویسیم. این کتابخانه کاری می کند که actionCreator های ما به جای برگردادن action های مختلف، یک تابع خاص برگردانند که خود آن تابع action نهایی ما را ارسال کند. بدین شکل می توانیم کدهای نامتقارن را در آن جای بدهیم. برای نصب این پکیج دستور زیر را در ترمینال خود وارد کنید:
npm install --save redux-thunk
پس از نصب شدن پکیج، وارد index.js شده و دستور import زیر را به آن اضافه کنید:
import thunk from 'redux-thunk';
حالا thunk که یک middleware می باشد به پروژه ما اضافه شده است. اگر یادتان باشد در جلسات قبلی به شما توضیح دادم که middleware ها به applyMiddleware اضافه می شدند بنابراین در همان فایل index.js این کار را نیز انجام دهید:
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(logger, thunk)));
حالا به thunk دسترسی کامل داریم و باید به actions.js برگردیم. همانطور که گفتم thunk به ما کمک می کند که به جای برگرداندن action، یک تابع را برگردانیم. در واقع به محض ارسال action از فایل actions.js کتابخانه thunk وارد عمل شده و آن action را مسدود می کند. سپس پس از انجام عملیات مورد نظر، دوباره همان action را ارسال می کند. اگر یادتان باشد middleware ها بین Action و reducer قرار می گرفتند.
مثلا من تابع storeResult را انتخاب می کنم و می گویم:
export const storeResult = ( res ) => { return dispatch => { setTimeout( () => { }, 2000 ); } };
همانطور که می بینید این تابع dispatch را به عنوان پارامتر دریافت می کند و به آن دسترسی دارد. حالا اگر بخواهیم درون آن و پس از گذشتن 2 ثانیه، یک Action جدید را برگردانیم به روش زیر عمل می کنیم:
export const storeResult = ( res ) => { return dispatch => { setTimeout( () => { dispatch(); }, 2000 ); } };
تابع dispatch به ما اجازه می دهد که action مورد نظر خود را ارسال کنیم و فعلا آن را خالی گذاشته ام. به دلیل اینکه اگر دوباره storeResult را به آن بدهیم برنامه را در یک حلقه بی نهایت قرار می دهیم، تابعی که دائما خودش را return می کند. راه حل چیست؟ برای حل این مشکل یک actionCreator دیگر نوشته می شود و سپس آن را به dispatch پاس می دهیم بنابراین ابتدا یک actionCreator به نام saveResult ایجاد می کنم:
export const saveResult = ( res ) => { return { type: STORE_RESULT, result: res }; }
حالا می توانیم آن را به dispatch پاس بدهیم:
export const saveResult = ( res ) => { return { type: STORE_RESULT, result: res }; } export const storeResult = ( res ) => { return dispatch => { setTimeout( () => { dispatch(saveResult(res)); }, 2000 ); } };
یادتان نرود که res را به saveResult پاس بدهید تا اطلاعات مورد نیاز به store ارسال شود. تمام فایل ها را ذخیره کرده و به مرورگر برگردید.
درون مروگر چند بار روی دکمه های مختلف کلیک کنید تا مقدار اولیه عوض شود و مطمئن شویم برنامه بدون مشکل است. پس از اطمینان از صحت کامل برنامه روی دکمه store result کلیک کنید. Li اضافه شده به صفحه باید پس از 2 ثانیه ظاهر شود، یعنی اطلاعات ما پس از 2 ثانیه ثبت می شود و کدهای نامتقارن را به درستی نوشته ایم! اگر به redux devtools خود نگاه کنیم متوجه می شویم که فقط تابع حاوی dispatch در این ابزار ظاهر می شود و تابع دیگر به طور کل نامرئی است. چرا؟ به دلیل اینکه در redux فقط action های متقارن حق ویرایش store را دارند.
از طرف دیگر اگر به قسمت کنسول مرورگر توجه کنیم اطلاعات بیشتری را مشاهده خواهیم کرد چرا که logger ما تک تک اتفاقات را log می کند:
البته در این قسمت اطلاعات ویرایش state نمایش داده نمی شود چرا که redux-thunk جلوی آن را می گیرد و خودش بعدا یک action را dispatch می کند.
در مرحله نهایی بهتر است که actions.js خود را به دو فایل تقسیم کنیم چرا که در پروژه های واقعی و بزرگ معمولا این کار را انجام می دهند. وارد پوشه actions شده و دو فایل جدید به نام counter.js و result.js ایجاد کنید. حالا باید در این پوشه سه فایل داشته باشید:
فایل actions.js را به actionTypes.js تغییر نام دهید. حالا Increment و Decrement و Add و Subtract که مربوط به counter.js هستند را درون آن قرار دهید:
import * as actionTypes from './actionTypes'; export const increment = () => { return { type: actionTypes.INCREMENT }; }; export const decrement = () => { return { type: actionTypes.DECREMENT }; }; export const add = ( value ) => { return { type: actionTypes.ADD, val: value }; }; export const subtract = ( value ) => { return { type: actionTypes.SUBTRACT, val: value }; };
توجه داشته باشید که actionTypes را نیز وارد این فایل کرده ایم تا به ثابت های تعریف شده در آن دسترسی داشته باشیم. در مرحله بعد باید saveResult و storeResult و deleteResult را نیز درون reducer.js قرار دهیم:
import * as actionTypes from './actionTypes'; export const saveResult = ( res ) => { return { type: actionTypes.STORE_RESULT, result: res }; } export const storeResult = ( res ) => { return (dispatch) => { setTimeout( () => { dispatch(saveResult(res)); }, 2000 ); } }; export const deleteResult = ( resElId ) => { return { type: actionTypes.DELETE_RESULT, resultElId: resElId }; };
فایل actionTypes نیز باید فقط حاوی اطلاعات زیر باشد:
export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const ADD = 'ADD'; export const SUBTRACT = 'SUBTRACT'; export const STORE_RESULT = 'STORE_RESULT'; export const DELETE_RESULT = 'DELETE_RESULT';
حالا یک فایل دیگر درون پوشه actions (کنار سه فایل دیگر) به نام index.js ایجاد می کنم. این فایل وظیفه export کردن تمام actionCreator ها را خواهد داشت. دستوری که در این فایل استفاده خواهم کرد احتمالا برایتان تازه باشد:
export {} from ''
این دستور به ما اجازه می دهد که بدون import کردن مقداری خاص درون این فایل، آن مقادیر را از یک فایل دیگر export کنیم. بنابراین:
export { add, subtract, increment, decrement } from './counter'; export { storeResult, deleteResult } from './result';
همانطور که مشاهده می کنید دو بار از این دستور جدید استفاده کرده ام و تمام موارد مورد نیاز خودم را export کرده ام تا در سر تا سر برنامه قابل دسترسی باشند. یعنی در آینده می توانیم فقط فایل index.js را import کرده و با این کار به تمامی فایل های بالا دسترسی داریم.
در مرحله بعد به پوشه reducers رفته و دستورات import فایل های counter.js و result.js را (البته آن import ای که مربوط به actionTypes است) به شکل زیر تغییر دهید:
import * as actionTypes from '../actions/actionTypes';
سپس به فایل Counter.js اصلی (درون پوشه containers و سپس Counter) می رویم و دستور import آن را نیز به شکل زیر تغییر می دهیم:
import * as actionCreators from '../../store/actions/index';
همانطور که گفتم از طریق فایل index به تمام محتویات export شده دسترسی خواهیم داشت.
توجه داشته باشید که استفاده از این همه تکنولوژی پیشرفته برای ساختن یک شمارنده ساده کار عقلانی نیست، هدف من از استفاده از این تکنولوژی ها یادگیری شما می باشد تا در آینده بتوانید در پروژه های بزرگ از این روش ها استفاده کنید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.