Redux پیشرفته: محل و منطق تغییر داده‌ها

Location and Logic of Data Changes

22 بهمن 1399
Redux پیشرفته: محل و منطق تغییر داده ها

در قسمت قبل با نحوه اجرای کدهای نامتقارن و redux-thunk آشنا شدید اما نوشتن کدهای نامتقارن مثل setTimeout تنها کارایی ActionCreator های ما نیست. مثلا هنگام ارسال درخواست های HTTP برای دریافت مقادیر از سرور می توانیم از همین actionCreator ها استفاده کنیم اما هنوز هم کارایی بیشتری قابل تصور است. مثلا به saveResult (درون فایل result.js در پوشه actions) دقت کنید:

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

ما در این تابع نتیجه را دریافت کرده و به سادگی یک Action را ارسال می کنیم اما می توان کار های بیشتری را نیز در این تابع انجام داد. به طور مثال اگر بگوییم:

export const saveResult = ( res ) => {
    const updatedResult = res * 2;
    return {
        type: actionTypes.STORE_RESULT,
        result: updatedResult
    };
}

حالا نتیجه را در 2 ضرب کرده ایم و آن را برگردانده ایم. شاید ضرب کردن نتیجه آنقدرها عقلانی نباشد اما هدف من از طرح چنین مثالی این است که بدانید می توانیم action ها را تغییر بدیم و درون کدهای خود انواع دستورات را بنویسیم. حالا اگر به مرورگر بروید و روی چند دکمه کلیک کرده و سپس store result را بزنید، مقدار شمارنده به صورت دو برابر در <li> ذخیره می شود.

همچنین شما می توانید همین منطق را در reducer خود پیاده سازی کنید. به طور مثال:

const reducer = ( state = initialState, action ) => {
    switch ( action.type ) {
        case actionTypes.STORE_RESULT : return updateObject( state, { results: state.results.concat( { id: new Date(), value: action.result } ) } );
        case actionTypes.DELETE_RESULT : return deleteResult(state, action);
    }
    return state;
};

این کد مربوط به reducer ما است و ما می توانیم آن را به شکل زیر تغییر بدهیم:

const reducer = ( state = initialState, action ) => {
    switch ( action.type ) {
        case actionTypes.STORE_RESULT : return updateObject( state, { results: state.results.concat( { id: new Date(), value: action.result * 2 } ) } );
        case actionTypes.DELETE_RESULT : return deleteResult(state, action);
    }
    return state;
};

یعنی دو برابر کردن می تواند در هر دو قسمت برنامه پیاده سازی شود و انتخاب با خود ما است. حالا با سوال اصلی روبرو می شویم: داده ها و action های خود را در کدام قسمت تغییر دهیم، reducer یا در actionCreator؟ هر دو روش کار می کنند و هیچ اشکالی در استفاده از هر دو آن ها نیست اما دانستن برخی از جزئیات در تصمیم گیری به ما کمک خواهد کرد:

  • ActionCreator: مخصوص اجرای کدهای نامتقارن هستند و بهتر است state برنامه را زیاد کنترل نکنند.
  • Reducer: فقط کدهای متقارن را اجرا می کنند و البته در redux همیشه وظیفه تغییر state را دارند.

در واقع هر دو طرف طرفداران خودشان را دارند بنابراین تصمیم با شماست اما من توصیفه می کنم که این کار را به reducer بسپارید. فرض کنید داده هایی را از سروری دریافت کرده اید، در ActionCreator خود در حد ممکن منطق کدنویسی پیاده نکنید مگر برخی از موارد جزئی. تغییرات اصلی را نیز به reducer ها بسپارید.

حالا اگر از بین این دو ActionCreator ها را انتخاب کرده اید باید با یک متد کمکی thunk آشنا شوید تا کارتان راحت تر شود. به طور مثال به StoreResult دقت کنید:

export const storeResult = ( res ) => {
    return (dispatch, getState) => {
        setTimeout( () => {
            dispatch(saveResult(res));
        }, 2000 );
    }
};

این کد یک کد نامتقارن بود و دستور setTimeout را برای شبیه سازی تاخیر سرور نوشته بودیم اما باید بدانید که redux-thunk توانایی پاس دادن یک آرگومان دیگر را هم دارد: getState. این آرگومان متدی است که state فعلی را به ما می دهد. شاید از خودتان بپرسید چرا به چنین چیزی نیاز دارم. پاسخ اینجاست که در برخی از پروژه ها نیاز است که قبل از dispatch کردن action مورد نظر، به state دسترسی داشته باشیم و وضعیت آن را بدانیم. مثلا فرض کنید بخواهیم داده هایی را برای یک کاربر خاص ذخیره کنیم و id این کاربر را در state برنامه داشته باشیم. اینجاست که getState به کمک ما آمده و state و در نتیجه id کاربر را به ما می دهد تا بتوانیم آن را درون action خود قرار دهیم و سپس آن را dispatch کنیم.

در پروژه شمارنده خودمان می توانیم state را به شکل زیر دریافت کنیم:

export const storeResult = ( res ) => {
    return (dispatch, getState) => {
        setTimeout( () => {
            const oldCounter = getState().ctr.counter;
            console.log(oldCounter);
            dispatch(saveResult(res));
        }, 2000 );
    }
};

با این روش، قبل از اینکه action را به reducer ارسال کنیم، می توانیم وضعیت شمارنده را از State گرفته و در کنسول مرورگر نمایش دهیم. این کد را ذخیره کرده و به مرورگر بروید. روی یک دکمه (مثلا Add 10) کلیک کرده و store result را بزنید چنین چیزی را در کنسول مرورگر مشاهده می کنید:

برگرداندن state فعلی قبل از dispatch شدن action
برگرداندن state فعلی قبل از dispatch شدن action

در واقع عدد 10 چاپ شده وضعیت شمارنده را قبل از dispatch کردن action نشان می دهد. بنابراین همانطور که گفتم اگر نیاز به استفاده از این متد دارید و می خواهید کدهای خود را درون actionCreator ها تغییر بدهید هیچ مشکلی نیست و از نظر کدنویسی روش بدی را انتخاب نکرده اید البته به شرطی که بیش از حد از آن استفاده نکنید.

روشی که من شخصا انتخاب می کنم استفاده از reducer برای تغییر داده ها است و برای اینکه مثل روش بالا به داده ها دسترسی داشته باشیم می توانیم از فایل Counter.js اصلی (پوشه container) استفاده کرده و هنگام dispatch کردن action، داده ها را به صورت پارامتر به آن پاس بدهیم:

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, id)),
        onDeleteResult: (id) => dispatch(actionCreators.deleteResult(id))
    }
};

به طور مثال در کد بالا و برای storeResult یک پارامتر خیالی دیگر به نام id را پاس داده ام تا این id به reducer ما ارسال شده و در آنجا به آن دسترسی داشته باشیم.

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

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