با Redux دوست باشیم (بخش دوم)


تو مقاله‌ی قبلی این مساله رو بررسی کردیم که Redux برای حل چه مشکلی به وجود اومده و چطور میتونه اون رو حل کنه. اما فرآیندی که ریداکس انجام میده چیه؟

قبل از اینکه وارد بررسی اجزا و فرآیند Redux بشیم، باید یه سوتفاهم رو حل کنیم. معمولا ما Redux رو همراه با ری‌اکت یاد گرفتیم و ممکنه تو ذهن ما این مساله جا افتاده باشه که ریداکس یه کتابخانه یا ماژول برای مدیریت State در ری‌اکته. اما Redux هم در Client و هم در Server میتونه استفاده بشه. یعنی یه پروژه NodeJs هم میتونه از Redux برای State Management استفاده کنه. اصولا هر اپلیکیشنی که از Vanilla Javascript استفاده میکنه میتونه از Redux بهره ببره.

برگردیم به سوال خودمون؛ Redux چطور کار میکنه؟ روشی که Redux برای کار استفاده میکنه خیلی ساده است. یه Store مرکزی وجود داره که تمام State هایی که در اپلیکیشن مشترک هستند رو ذخیره میکنه و کامپوننت ها بدون اینکه مشکلات پاس دادن داده‌ها بین هم رو داشته باشن می‌تونن داده‌های دلخواهشون رو از Store بگیرن و یا با رعایت یه فرمت خاص داده بفرستن.
Redux از سه بخش اساسی و مهم تشکیل شده:

  • Store
  • Action
  • Reducer

برای شناختن اجزای Redux از Action شروع می‌کنیم. Action یه آبجکت ساده است. ما با استفاده از Actionها تغییری که باید در Store رخ بده رو توصیف می‌کنیم. هر Action از یه Type و یه سری داده‌های اضافه‌تر تشکیل شده. داده‌های اضافه بستگی به اون Action دارندو به طور مثال، اگر یه فرآیند لاگین رو در نظر بگیریم، نام کاربری و گذرواژه داده‌هایی هستند که در آبجکت ما قرار می‌گیرند.
یه عادت خوب برنامه‌نویسی اینه که تمام داده‌های موردنظر رو به صورت یک آبجکت درآورده، داخل Action قرار بدیم که معمولا با نام Payload دیده می‌شه.

اما Type بخش اجباری هر Action به حساب میاد. یه رشته (String) که به صورت حروف بزرگ نوشته شده و گویای عملی خواهد بود که قراره انجام بشه. در مورد همون مثال لاگین، آبجکت ما به شکل زیر خواهد بود:

{
    type: &quotLOGIN&quot,
    payload: {
        username: 'foo',
        password: 'bar'
    }
}

هر اکشنی توسط یه action creator ساخته میشه. Action Creator فانکشنیه که صرفا ًآبجکت مورد نظر ما رو می‌سازه و هیچ کاری غیر از این نباید انجام بده. به طور مثال:

const Login = payload => ({
    type: &quotLOGIN&quot,
    payload,
})

و در نهایت آبجکتی که توسط این Action Creator ساخته می‌شه باید توسط یه فانکشن دیگه که جلوتر باهاش آشنا می‌شیم استفاده می‌شه .(Dispatch)
حالا ممکنه این سوال پیش بیاد که چه چیزهایی اکشن به حساب میان؟ چیزی که من به نظرم می‌رسه اینه که هر ایونت، اینتراکشن کاربر، فراخوانی API و ... که روی State موجود در Store تاثیر مستقیم یا غیر مستقیم می‌ذاره می‌تونه کاندیدای Action شدن باشه.
می‌تونیم تو یک مثال کوچیک Action رو جمع بندی کنیم: (مثال از وبسایت ریداکس برداشته شده)

/*
* Action Types
*/

export const ADD_TODO = &quotADD_TODO&quot

/*
* Action Creators
*/

export function addTodo(payload) {
    return {
        type: ADD_TODO,
        payload
    }
}




شاید Reducer چیزی شبیه به این باشه
شاید Reducer چیزی شبیه به این باشه

اگر بخوام بین اجزای Redux مهم‌ترین جز رو انتخاب کنم، رای من به احتمال خیلی زیاد Reducer خواهد بود. تصویری که من از Reducer برای خودم ترسیم می‌کنم چیزی شبیه به یه خط تولیده که به ازای هر ورودی خروجی مشخصی داره. کافیه که ورودی مطابق با استانداردهای خط تولید باشه. اون وقت حتماً خروجی قابل انتظاری ازش خواهیم داشت .

به طور کلی وظیفه Reducer ایجاد یه State جدید به ازای اکشن و State ورودی است ( ایجاد State جدید، ری‌اکشن Reducer به هر اکشنه). Reducer، همون طور که از اسمش پیداست، بر مبنای تابع Reduce ساخته شده. تابع Reduce وظیفه داره که یه آرایه رو به یه مقدار واحد تبدیل کنه. Reducerها توابع Pure هستند که به عنوان ورودی:

  • مقدار فعلی State اپلیکیشن که در Store ذخیره شده
  • اکشنی که فراخوانی شده

می‌گیرن و به عنوان خروجی یک State جدید برمی‌گردونن.

حالا فقط کافیه که بدونیم به چه توابعی Pure Function میگن. توابعی که وقتی یک ورودی یکسان میگیرن و بدون ایجاد Side Effect خروجی یکسانی تولید میکنن Pure Function هستند. به نظر میاد تنها نقطه‌ی مبهم توی این تعریف عبارت Side Effect باشه.

هر تغییر State در اپلیکیشن که از بیرون فانکشنی که فراخوانی شده قابل رویت باشه و با مقداری که اون فانکشن برمی‌گردونه متفاوت باشه قطعا Side Effect هست. مثلا تغییر هر متغییر خارجی یا آبجکت ، نوشتن در کنسول، روی صفحه یا داخل یک فایل و ...
برای جمع بندی بهتره یه قطعه کد مربوط به Reducer ها رو با هم ببینیم :

import { ADD_TODO } from './actions';

const initialState = {
    todos: [],
}

function TodoReducer (state = initialState, action) {
    switch(action.type) {
        case ADD_TODO:
            return {
                ...state,
                todos: [ ...state.todos, action.payload ]
            }
    }
}
export default TodoReducer

فقط به عنوان آخرین نکته ها، ما خود State رو تغییر نمیدیم بلکه با استفاده از اون و Action ورودی یک State جدید می‌سازیم و برمیگردونیم. و علاوه بر اون ما میتونیم چندین Reducer داشته باشیم و با استفاده از combineReducer اونها رو باهم تلفیق کنیم.




و بالاخره آخرین بخش یعنی Store، وظیفه داره تمام درخت State اپلیکیشن رو ذخیره کنه. به صورت مستقیم نمیشه مقدارش رو تقییر داد و باید یک Action فراخوانی بشه تا توسط Reducer مقدار جدید تولید و توی Store ذخیره و قابل دسترسی باشه. Store یه کلاس نیست بلکه یه آبجکت ساده است که چندتا متد محدود برای دسترسی بهش وجود داره. وظایف Store :

  • نگه داری از Application State
  • دسترسی به State با استفاده از getState
  • به روز رسانی State با استفاده از dispatch
  • ثبت یک Listener با استفاده از subscribe
  • پاک کردن Listener با استفاده از تابعی که توسط subscribe برگردونده میشه.
  • جایگزین کردن یک Reducer با Reducer دیگر با استفاده از replaceReducer که به صورت hot reloading عمل میکنه

برای ساخت Store هم کافیه به شکل زیر عمل کنیم :

import { createStore } from 'redux';
import todoReducer from './reducer';

const store = createStore(todoReducer);

store.dispatch(addTodo('Read the docs'));

توی این قطعه کد با استفاده از Reducer که قبلا ساختیم و متد createStore که از redux گرفتیم یه Store ساختیم و با استفاده از متد dispatch که روی store داریم میتونیم مقدار داخلش رو تغییر بدیم و به Todo ها یه todo جدید اضافه کنیم.



وقت جمع بندیه.

اپلیکیشن یه action رو dispatch میکنه . store وظیفه داره state فعلی که در store ذخیره شده و action رو به reducer (یا rootReducer که تلفیق چندین Reducer هست) تحویل میده. و در نهایت state جدید تولید میشه و داخل store ذخیره میشه. بعد از ذخیره شدن تمام کامپوننت هایی که به store وصل شدن (subscriber) تغییرات جدید رو میگیرن و خودشون رو به روزرسانی میکنن.

فقط یه سوال هنوز بی جواب مونده :‌ اگه یه Async Action داشتیم Redux چطور عمل میکنه؟