Redux پیشرفته: استفاده از توابع کمکی

Using Auxiliary Functions

15 مرداد 1399
Redux پیشرفته: استفاده از توابع کمکی

تا این قسمت، مطالب بسیار زیادی در مورد redux یاد گرفته ایم و با مفاهیم پیشرفته ای مثل action creator و middleware آشنا شده ایم اما در این قسمت می خواهم در مورد reducer های پیشرفته تر صحبت کنم. در حال حاضر reducer های ما ابتدایی نیستند چرا که آن ها را به دو قسمت (دو فایل) تقسیم کرده ایم و بعدا به صورت مجموع از آن ها استفاده کرده ایم اما دستور Switch ما در هر دو فایل هنوز نسبتا طولانی است:

const reducer = ( state = initialState, action ) => {
    switch ( action.type ) {
        case actionTypes.INCREMENT:
            const newState = Object.assign({}, state);
            newState.counter = state.counter + 1;
            return newState;
        case actionTypes.DECREMENT:
            return {
                ...state,
                counter: state.counter - 1
            }
        case actionTypes.ADD:
            return {
                ...state,
                counter: state.counter + action.val
            }
        case actionTypes.SUBTRACT:
            return {
                ...state,
                counter: state.counter - action.val
            }
    }
    return state;
};

و همینطور:

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:
            // const id = 2;
            // const newArray = [...state.results];
            // newArray.splice(id, 1)
            const updatedArray = state.results.filter(result => result.id !== action.resultElId);
            return {
                ...state,
                results: updatedArray
            }
    }
    return state;
};

به همین خاطر پاک سازی چنین reducer هایی یکی از کار های توصیه شده است. توجه کنید که این کار اجباری نیست بلکه برای زیباتر شدن و تمیزتر شدن کدها است و نشان می دهد که شما یک برنامه نویس حرفه ای هستید. به همین خاطر من می خواهم چند فاکتور پاک سازی reducer را نشان بدهم و بهتر است شما هم قدم به قدم کار را دنبال کنید.

اولین فاکتور ما immutability یا یا همان تغییرات immutable است. در دستور Switch بالا هر زمان که یک state جدید را برمی گردانیم، یک شیء جدید نیز می سازیم:

case actionTypes.ADD:
    return {
        ...state,
        counter: state.counter + action.val
    }

همانطور که می دانید ما تنها می خواهیم یک شیء update شده را برگردانیم اما برای جلوگیری از بروز خطاهای مختلف ابتدا شیء State قبلی را کپی می کنیم و سپس آن را به همراه تغییرات به صورت یک شیء جدید برمی گردانیم. این کار مشکل خاصی ندارد اما چند بار نویسی کدها است. ما می توانیم توابع کمکی برای بروز رسانی آرایه ها و اشیاء داشته باشیم تا برای ویرایش اشیاء و آرایه ها دیگر نیازی به نوشتن کدها نداشته باشیم بلکه فقط یک تابع را صدا بزنیم.

برای انجام این کار به پوشه store رفته و در آن یک فایل جدید به نام utility.js ایجاد کنید. حالا درون این فایل یک تابع ES6 (از نوع Arrow function) ایجاد می کنم:

const updateObject = () => {

};

این تابع دو پارامتر می گیرد: شیء قدیمی یا همان شیء ای که قرار است ویرایش یا کپی شود و البته مقادیر جدید که قرار است درون شیء قدیمی قرار بگیرند، بنابراین:

const updateObject = (oldObject, updatedValues) => {

};

حالا باید مثل همیشه عمل کنیم:

  • اشیاء و آرایه ها در جاوا اسکریپت reference-typed هستند بنابراین اول از همه باید از شیء قدیمی یک کپی بگیریم. برای کپی گرفتن از این شیء مثل همیشه از اپراتور spread استفاده می کنیم.
  • سپس فرض می کنیم که مقدار updatedValues نیز قرار است یک شیء جاوا اسکریپتی باشد بنابراین آن را هم با استفاده از اپراتور spread در شیء جدید قرار می دهیم. این فرض ما به نوعی یک قرارداد با خودمان است. یعنی خودمان همیشه می دانیم که باید یک شیء جاوا اسکریپتی درون این تابع استفاده شود حتی اگر فقط یک خصوصیت داشته باشد.
  • از آنجایی که می خواهیم از این تابع در فایل های دیگر استفاده کنیم باید دستور export را نیز در ابتدای آن قرار دهیم.
export const updateObject = (oldObject, updatedValues) => {
    return {
        ...oldObject,
        ...updatedValues
    }
};

تابع ما به همین سادگی نوشته شده است. حالا می توانیم از آن در فایل های reducer خود استفاده کنیم. به پوشه reducers و سپس فایل counter.js بروید و تابع بالا را در آن وارد کنید:

import { updateObject } from '../utility';

حالا می توانیم به جای نوشتن این کد:

case actionTypes.SUBTRACT:
    return {
        ...state,
        counter: state.counter - action.val
    }

تابع خودمان را صدا بزنیم و مقدار جدید را به آن پاس بدهیم:

case actionTypes.SUBTRACT:
            return updateObject(state, {counter: state.counter - action.val});

سپس تمام کدهایی را که به صورت دستی نوشته بودیم با این تابع بازنویسی می کنیم.

const reducer = ( state = initialState, action ) => {
    switch ( action.type ) {
        case actionTypes.INCREMENT:
            return updateObject(state, {counter: state.counter + 1});
        case actionTypes.DECREMENT:
            return updateObject(state, {counter: state.counter - 1});
        case actionTypes.ADD:
            return updateObject(state, {counter: state.counter + action.val});
        case actionTypes.SUBTRACT:
            return updateObject(state, {counter: state.counter - action.val});
    }
    return state;
};

همانطور که می بینید به جای return کردن یک object literal، تابع updateObject را صدا زده ایم و پارامتر اول آن را state و پارامتر دوم آن را مقدار جدید داده ایم. اگر این دستور switch را با دستور switch قبلی مقایسه کنید متوجه خواناتر شدن و تمیزتر شدن آن می شوید.

حالا به فایل result.js می رویم و  تابع updateObject را در آن هم import می کنیم:

import { updateObject } from '../utility.js';

در این فایل یک آرایه وجود دارد که آن را با استفاده از filter به روز رسانی کرده ایم و من آن را به همین شکل باقی می گذارم، چرا که ویرایش کردن آرایه ها شدیدا به نوع آرایه و نحوه ویرایش آن بستگی دارد. آسان تر است که آن را به همین شکل باقی بگذاریم اما بقیه موارد را با updateObject می نویسیم:

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:
            const updatedArray = state.results.filter(result => result.id !== action.resultElId);
            return updateObject(state, { result: updatedArray });
    }
    return state;
};

حالا کدهای خود را ذخیره کرده و به مرورگر برگردید. با کلیک کردن روی دکمه های Add و Subtract و غیره باید مقدار شمارنده بدون هیچ گونه خطایی تغییر کند و همچنین با کلیک روی save result مقدار صحیح باید ذخیره شود. توجه داشته باشید که در reducer (کد بالا) مقدار را در 2 ضرب کردیم بنابراین 2 برابر مقدار شمارنده باید در <li> ذخیره شود.

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

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

دیدگاه‌های شما

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