Redux پیشرفته: آشنایی با ActionCreator ها

ActionCreator

14 مرداد 1399
Redux پیشرفته: آشنایی با ActionCreator ها

در این قسمت می خواهیم با روش اجرای کدهای نامتقارن آشنا شویم. برنامه شمارنده ما یک برنامه بسیار ساده است و هیچ ارتباطی با سرور ندارد به همین خاطر ما از setTimeout استفاده می کنیم تا وجود یک سرور را شبیه سازی کنیم. مثلا store بعد از وقفه ایجاد شده توسط setTimeout تغییر کند.

در حال حاضر کد reducer ما در result.js به شکل زیر است:

const reducer = ( state = initialState, action ) => {
    switch ( action.type ) {
        case actionTypes.STORE_RESULT:
            return {
                ...state,
                results: state.results.concat({id: new Date(), value: action.result})
            }
        case actionTypes.DELETE_RESULT:
            // بقیه کدها

ما نمی توانیم دستور setTimeout را درون آن بنویسیم. چرا؟ به دلیل اینکه دستور setTimeout دارای تاخیر است. مثلا فرض کنید به دستور setTimeout بگوییم پس از 2 ثانیه اجرا شود. تا زمانی که این دستور اجرا شود ما از دستور switch خارج شده ایم بنابراین کل کد باطل می شود. همچنین نمی توانیم از promise ها نیز استفاده کنیم چرا که reducer شما منتظر دریافت هیچ promise ای نیست بنابراین در ظاهر راهی برای اجرای کدهای نامتقارن وجود ندارد.

راه حل ما برای اجرای کدهای نامتقارن ActionCreator ها هستند! برای آشنایی با این عناصر جدید یک پوشه جدید به نام actions در پوشه store ایجاد کرده و فایل Actions.js خود را به درون آن منتقل کنید. در حال حاضر تمام action های ما در این فایل قرار دارند:

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';

اما روش دیگری برای ساخت action ها وجود دارد که به آن ActionCreator می گوییم. در واقع ActionCreator فقط یک تابع هستند که یک action را برمی گردانند! در حال حاضر برای ساخت action های خودمان از mapDispatchToProps درون فایل Counter.js استفاده می کنیم:

const mapDispatchToProps = dispatch => {
    return {
        onIncrementCounter: () => dispatch({type: actionTypes.INCREMENT}),
        onDecrementCounter: () => dispatch({type: actionTypes.DECREMENT}),
        onAddCounter: () => dispatch({type: actionTypes.ADD, val: 10}),
        onSubtractCounter: () => dispatch({type: actionTypes.SUBTRACT, val: 15}),
        onStoreResult: (result) => dispatch({type: actionTypes.STORE_RESULT, result: result}),
        onDeleteResult: (id) => dispatch({type: actionTypes.DELETE_RESULT, resultElId: id})
    }
};

بنابراین در همین فایل action.js یک تابع جدید می سازیم تا actionCreator ما باشد:

const increment = () => {
    return {
        type: INCREMENT
    };
};

این تابع اولین actionCreator ما است، یک Action برمی گرداند. برای استفاده از این actionCreator ابتدا باید آن را export کنیم:

export const increment = () => {
    return {
        type: INCREMENT
    };
};

سپس به فایل Counter.js بروید و دستور import زیر را حذف کنید:

import * as actionTypes from '../../store/actions';

و به جای آن دستور زیر را اضافه کنید:

import { increment } from '../../store/actions/actions';

حالا به انتهای همین فایل و به mapDispatchToProps بروید تا از این تابع import شده استفاده کنیم:

const mapDispatchToProps = dispatch => {
    return {
        onIncrementCounter: () => dispatch(increment()),
        // بقیه کدها

همانطور که می بینید آن را به صورت یک تابع صدا زده ایم. توجه داشته باشید که تا این قسمت هنوز هیچ کد نامتقارنی ننوشته ایم. در مرحله بعد باید این کار را برای تک تک action های خود انجام دهیم. من DECREMENT را نیز اضافه می کنم:

export const increment = () => {
    return {
        type: INCREMENT
    };
};

export const decrement = () => {
    return {
        type: DECREMENT
    };
};

برای action بعدی که Add است نیاز به payload داریم. یعنی تابع ما به عنوان پارامتر باید مقدار اضافه شده را دریافت کند چرا که این مقدار قبلا به صورت دستی  در یک شیء ساده قرار داده شده بود. به نظر شما باید چه کار کنیم؟

 onAddCounter: () => dispatch({type: actionTypes.ADD, val: 10}),

همانطور که می بینید مقدار 10 را برای این action در نظر گرفته بودیم بنابراین باید همین مقدار را به تابع مورد نظرش پاس بدهیم بنابراین در فایل actions.js می گوییم:

export const add = (value) => {
    return {
        type: ADD,
        val: value
    };
};

یعنی ما انتظار دریافت مقداری را هنگام اجرا شدن تابع داریم. همین کار را برای SUBTRACT نیز انجام می دهیم:

export const subtract = (value) => {
    return {
        type: SUBTRACT,
        val: value
    };
};

همین کار را با بقیه action هایمان نیز انجام می دهیم تا کل فایل actions.js به شکل زیر در بیاید:

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';

export const increment = () => {
    return {
        type: INCREMENT
    };
};

export const decrement = () => {
    return {
        type: DECREMENT
    };
};

export const add = (value) => {
    return {
        type: ADD,
        val: value
    };
};

export const subtract = (value) => {
    return {
        type: SUBTRACT,
        val: value
    };
};

export const storeResult = (res) => {
    return {
        type: STORE_RESULT,
        result: res
    };
};

export const deleteResult = (resElId) => {
    return {
        type: DELETE_RESULT,
        resultElId: resElId
    };
};

توجه داشته باشید که برای هر کدام از آن ها val را قرار نداده ایم بلکه نام خصوصیات اشیاء برگردانده شده را طبق موارد قبلی مانند result و resultElId تعیین کرده ایم.

حالا می توانیم به فایل counter.js برگردیم و این بار دستور import زیر را:

import { increment } from '../../store/actions/actions';

به شکل زیر تغییر بدهیم:

import * as actionCreators from '../../store/actions/actions';

حالا باید به increment پیشوند actionCreators را اضافه کنیم:

        onIncrementCounter: () => dispatch(actionCreators.increment()),

در مرحله بعد نیز تمام کدهای قسمت mapDispatchToProps را به همین شکل تصحیح می کنیم:

const mapDispatchToProps = dispatch => {
    return {
        onIncrementCounter: () => dispatch(actionCreators.increment()),
        onDecrementCounter: () => dispatch(actionCreators.decrement()),
        onAddCounter: () => dispatch(actionCreators.add(10)),
        onSubtractCounter: () => dispatch(actionCreators.subtract(15)),
        onStoreResult: (result) => dispatch(actionCreators.storeResult(result)),
        onDeleteResult: (id) => dispatch(actionCreators.deleteResult(id))
    }
};

دقت کنید که مقادیر مورد نیاز را از همین قسمت پاس داده ایم. حالا کدهای خود را با actionCreator ها نوشته ایم و در واقع فعلا چیزی تغییر نکرده است. مرحله نهایی ویرایش کردن دستور import درون دو reducer در پوشه reducers است. در این پوشه دو فایل counter.js و result.js وجود دارد که باید دستورات import آن ها را به شکل زیر ویرایش کنید:

import * as actionTypes from '../actions/actions';

حالا می توانید به مرورگر رفته و کدها را تست کنید، برنامه باید بدون هیچ مشکلی اجرا شود.

سوال: آیا استفاده از actionCreator ها به شکل فعلی (کدهای متقارن) مشکلی دارد؟

پاسخ: استفاده از actionCreator ها به شکلی که ما از آن ها استفاده کرده ایم مشکلی ندارد اما شاید به نوعی اضافه نویسی تلقی شود. اگر بخواهید کدهای خود را به صورت متقارن بنویسید نیازی به actionCreator ها نخواهید داشت. من کدهایمان را به این نسخه تبدیل کردم تا در جلسات بعد کدهای نامتقارن بنویسیم اما اگر کسی دوست دارد کدهای متقارن خود را با actionCreator بنویسد مشکلی به وجود نخواهد آمد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری دوره جامع آموزش ری اکت توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

جابر
17 فروردین 1400
سلام من یک کد دارم برای کانفیگ کردن axios هستش ودر موردش کمی توضیح میخواهم.آموزش های شمابسیار عالی می باشد وتمام سوالاتی که داشتم رو جواب هایش رو پیدا کردم.خیلی ممنون هستم از شما export const fetchWrapper = { get, post, put, delete: _delete } function get(url) { const requestOptions = { method: 'GET', headers: authHeader(url) }; return fetch('users/', requestOptions).then(handleResponse,console.log(handleResponse.data.data)) ;; } function post(url, body) { const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeader(url) }, credentials: 'include', body: JSON.stringify(body) }; return fetch(url, requestOptions).then(handleResponse); } این یک کانفیگ کردن دارد.اگر بازم مشکلی بود بگید تا برنامه کلا ارسال کنم

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.