مفهوم Reducer در جاوااسکریپت با معرفی Redux به عنوان یک کتابخانه مدیریت state در ری اکت بیش از پیش مورد استفاده قرار می گیرد. اما نگران نباشید. برای یادگیری و تسلط بر Reducer ها، نیازی به یادگیری Redux نیست. اما استفاده از reducer ها در ری اکت و با معرفی useReducer hook بی اندازه پر اهمیت شده است. برای یادگیری ری اکت به بخش آموزش ری اکت مراجعه کنید. همچنین می توانید کاربرد useReducer در react را مطالعه کنید.
Reducer در حقیقت تابعی است که دو آرگومان به عنوان ورودی دریافت می کند (state جاری و یک action) و بر مبنای این دو یک state جدید بر می گرداند. ساختار این تابع به شکل زیر است:
(state, action) => newState
به عنوان مثال، در سناریوی زیر می خواهیم یک عدد را یک واحد افزایش دهیم:
function counterReducer(state, action) {
return state + 1;
}
و اگر بخواهیم به صورت arrow function آن را بیان کنیم داریم:
const counterReducer = (state, action) => {
return state + 1;
};
در مثال فوق state جاری یک عدد است و reducer function این عدد را یک واحد افزایش می دهد. چنانچه state را به count تغییر نام دهیم شاید برای توسعه دهندگان تازه کار واضح تر باشد. ولی در هر حالتی باید به خاطر داشته باشید که count همچنان یک state است:
const counterReducer = (count, action) => {
return count + 1;
};
Reducer function تابعی است بدون هیچ site-effect ی و این یعنی همیشه و در همه حال تعداد آرگومان های ورودی (state و action) و خروجی (new state) یکسان است.
آرگومان دوم که action نام دارد در حقیقت آبجکتی است که خصوصیتی به نام type دارد. Reducer بر مبنای این خصوصیت، تصمیم می گیرد چه state ی باید به عنوان state جدید برگردانده شود:
const counterReducer = (count, action) => {
if (action.type === 'INCREASE') {
return count + 1;
}
if (action.type === 'DECREASE') {
return count - 1;
}
return count;
};
اگر برای خصوصیت type هیچ مقداری تعیین نشود یا برای مقدار ارسالی هیچ منطقی پیاده سازی نشود، state جاری بدون تغییر به عنوان خروجی تابع برگردانده می شود. معمولا برنامه نویسان ترجیح می دهند به جای استفاده از if-else از switch case در پیاده سازی بدنه reducer function استفاده کنند. هر چند هیچ الزامی وجود ندارد ولی به عنوان یک قاعده کلی می توانید از آن تبعیت کنید. در کد زیر همان منطق قبل اما با switch case پیاده سازی شده است:
const counterReducer = (count, action) => {
switch (action.type) {
case 'INCREASE':
return count + 1;
case 'DECREASE':
return count - 1;
default:
return count;
}
};
دقت کنید در این مثال count را به عنوان state در نظر گرفته ایم ولی در برنامه های واقعی اغلب، state شما یک آبجکت است که می تواند پیچیده هم باشد. به عنوان مثال count می تواند یک خصوصیت از state object شما باشد:
const counterReducer = (state, action) => {
switch (action.type) {
case 'INCREASE':
return { ...state, count: state.count + 1 };
case 'DECREASE':
return { ...state, count: state.count - 1 };
default:
return state;
}
};
در کد فوق دو نکته مهم وجود دارد که به ترتیب آن ها را بررسی می کنیم:
- Stateی که توسط reducer function پردازش می شود باید غیر قابل تغییر یا immutable باشد
تغییر ناپذیر بودن یا immutability به این معنی است که state ِ ورودی (که به شکل آرگومان به reducer ارسال می شود)، مستقیما قابل تغییر نیست. در نتیجه reducer function باید همیشه یک state object ِ جدید برگرداند.
- برای تضمین immutability ما از JavaScript spread operator استفاده می کنیم. این اپراتور، یک آبجکت جدید از state ِ ورودی و بخشی که تغییر داده ایم (خصوصیت count) ایجاد می کند. با این کار ما تضمین می کنیم که دیگر خصوصیات آبجکت state، بدون تغییر باقی می مانند و فقط خصوصیتی که تغییر کرده است به همراه سایر خصوصیات به عنوان یک state ِ جدید برگردانده می شود.
اجازه بدهید این دو نکته مهم را در مثال زیر بیشتر بررسی کنیم. در مثال زیر می خواهیم نام خانوادگی شخصی را که در آبجکتِ person قرار دارد تغییر دهیم. دقت کنید person را به عنوان state در نظر می گیریم:
const personReducer = (person, action) => {
switch (action.type) {
case 'INCREASE_AGE':
return { ...person, age: person.age + 1 };
case 'CHANGE_LASTNAME':
return { ...person, lastname: action.lastname };
default:
return person;
}
};
نحوه فراخوانی و استفاده از reducer function به صورت زیر است:
const initialState = {
firstname: 'Liesa',
lastname: 'Huppertz',
age: 30,
};
const action = {
type: 'CHANGE_LASTNAME',
lastname: 'Wieruch',
};
const result = personReducer(initialState, action);
expect(result).to.equal({
firstname: 'Liesa',
lastname: 'Wieruch',
age: 30,
});
همانطور که می بینید با استفاده از spread operator همه خصوصیات state ِ جاری را برای state ِ جدید در نظر گرفته ایم اما خصوصیت lastname را تغییر داده ایم. به همین دلیل است که یکی از مهمترین کاربردهای spread operator ، پیاده سازی مفهوم immutability است. یعنی عدم تغییر state به شکل مستقیم.
آبجکت ِ action غیر از خصوصیت type، خصوصیتی دیگری به نام payload می تواند داشته باشد که البته این خصوصیت اختیاری است. payload در حقیقت زمانی استفاده می شود که می خواهیم اطلاعات بیشتری به reducer برای تغییر state ارسال کنیم. در همین مثال، reducer، در مورد نام خانوادگی جدید هیچ اطلاعاتی ندارد. ما باید نام خانوادگی جدید را از طریق payload ارسال کنیم. به عبارت دقیق تر action object ِ ما باید به شکل زیر تغییر کند:
const action = {
type: 'CHANGE_LASTNAME',
payload: {
lastname: 'Wieruch',
},
};
علاوه بر تغییر فوق لازم است reducer function را به شکل زیر تغییر دهیم:
const personReducer = (person, action) => {
switch (action.type) {
case 'INCREASE_AGE':
return { ...person, age: person.age + 1 };
case 'CHANGE_LASTNAME':
return { ...person, lastname: action.payload.lastname };
default:
return person;
}
};
به طور خلاصه هدف کلی reducer ها، تغییر state از وضعیت A به وضعیت B است که این کار به کمک action ها و از طریق دو خصوصیت type و payload انجام می شود. reducer تابعی است که دو آرگومان به نام های state و action از ورودی دریافت می کند و یک state جدید را به عنوان خروجی بر می گرداند. مهمترین خصوصیت reducer function ها تضمین immutability است. به این معنی که state نباید به شکل مستقیم تغییر کند. در عوض تغییرات روی state به شکل غیر مستقیم و با برگرداندن یک state جدید باید انجام شود. اینکار را می توان با استفاده از spread operator به سادگی انجام داد. علاوه بر موارد فوق، reducer function، به یک action object نیاز دارد که این آبجکت یک خصوصیت الزامی به نام type و یک خصوصیت اختیاری به نام payload دارد. از خصوصیت type به منظور پیاده سازی منطق های مختلف استفاده می شود که اغلب از switch case استفاده می شود و از خصوصیت payload برای ارسال اطلاعات بیشتر به reducer function استفاده می کنیم.
در پایان در نظر داشته باشید، reducer یکی از مفاهیم پر کاربرد در React است. useReducer پیاده سازی reducer در کتابخانه ری اکت است. توصیه می کنم چنانچه به این کتابخانه علاقه مند هستید، آموزش ری اکت را در وب سایت کلاسبن دنبال کنید.