ارسال action از درون کامپوننت

Sending Action from Component

22 بهمن 1399
ارسال action از درون خود کامپوننت

در قسمت قبل توانستیم با استفاده از prop ای به نام ctr شمارنده خود را از redux بگیریم اما هیچ کدام از دکمه هایی که تعریف کرده بودیم، کار نمی کنند (دکمه های increment و ...) بنابراین باید action های مربوطه را از داخل همین کامپوننت ارسال کنیم. زمانی که در فایل redux-basics کار می کردیم می توانستیم به سادگی بگوییم store.dispatch و مشکلی نبود اما در کامپوننت Counter.js به store دسترسی مستقیم نداریم. ما می توانیم از connect استفاده کنیم تا دقیقا مثل قبل اطلاعات دیگری را نیز علاوه بر ctr ارسال کنیم:

const mapStateToProps = state => {
    return {
        ctr: state.counter
    };
};

const mapDispatchToProps = dispatch => {
    return {

    };
};

همانطور که می بینید من یک ثابت به نام mapDispatchToProps ایجاد کرده ام که عملیات dispatch را انجام می دهد. در واقع ما به Store خودمان دسترسی مستقیم نداریم اما تابع dispatch در بالا که توسط پکیج react-redux ارائه شده است به ما اجازه دسترسی و تعیین action ها را می دهد. همانطور که قبلا گفتیم action ها معمولا یک شیء جاوا اسکریپتی هستند بنابراین ما هم درون این تابع یک شیء جاوا اسکریپتی را return می کنیم. همچنین نام key های درون این شیء به سلیقه شما بستگی دارد، من شیء خودم را به شکل زیر می نویسم:

const mapDispatchToProps = dispatch => {
    return {
        onIncrementCounter: () => dispatch({type: 'INCREMENT'})
    };
};

در واقع تابع dispatch درون شیء بالا از طریق onIncrementCounter قابل دسترسی خواهد بود، یعنی اگر onIncrementCounter را به صورت یک تابع اجرا کنیم (مثلا ()onIncrementCounter روی یک event-handler)، تابع dispatch درون آن نیز اجرا می شود. همانطور که قبلا گفته بودیم action ها یک key به نام type دارند که مقدار آن را خودتان تعیین خواهید کرد. حالا می توانیم این شیء را به عنوان آرگومان دوم به connect ارسال کنیم:

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

نکته: اگر کامپوننتی داشتید که کارش فقط dispatch کردن Action ها بود و به پارامتر اول connect احتیاج نداشتید می توانید به سادگی مقدار آن را null قرار دهید:

export default connect(null, mapDispatchToProps)(Counter);

همچنین اگر نیازی به dispatch کردن action نداشتید می توانید مثل جلسه قبل مقدار آن را خالی بگذارید:

export default connect(mapStateToProps)(Counter);

در حال حاضر قسمت JSX ما به صورت زیر است:

return (
    <div>
        <CounterOutput value={this.props.ctr} />
        <CounterControl label="Increment" clicked={() => this.counterChangedHandler('inc')} />
        <CounterControl label="Decrement" clicked={() => this.counterChangedHandler('dec')} />
        <CounterControl label="Add 5" clicked={() => this.counterChangedHandler('add', 5)} />
        <CounterControl label="Subtract 5" clicked={() => this.counterChangedHandler('sub', 5)} />
    </div>
);

همانطور که می بینید این کد ها مربوط به زمانی هستند که ما شمارنده را از طریق state درون کامپوننت (پیش فرض react) مدیریت می کردیم اما حالا که از redux استفاده می کنیم هیچ کدام از این دکمه ها تاثیری در شمارنده نخواهند داشت. در واقع این دکمه ها هنوز state قدیمی برنامه را تغییر می دهند اما در جلسه قبل کاری کردیم که برای نمایش شمارنده دیگر از آن state استفاده نمی کنیم، به همین علت تغییرات را نیز نمی بینیم. برای حل این مشکل من شیء تازه ساخته شده خودمان را به جای دستور بالا برای increment جایگذاری می کنم:

return (
    <div>
        <CounterOutput value={this.props.ctr} />
        <CounterControl label="Increment" clicked={this.props.onIncrementCounter} />
        <CounterControl label="Decrement" clicked={() => this.counterChangedHandler('dec')} />
        <CounterControl label="Add 5" clicked={() => this.counterChangedHandler('add', 5)} />
        <CounterControl label="Subtract 5" clicked={() => this.counterChangedHandler('sub', 5)} />
    </div>
);

حالا با کلیک شدن روی دکمه increment، تابع dispatch اجرا خواهد شد. نکته مهم این است که به اشتباه کد بالا را به شکل زیر ننویسید:

<CounterControl label="Increment" clicked={this.onIncrementCounter} />

همانطور که قبلا توضیح دادم، پکیج react-redux به ما کمک می کند تا به redux و store خود دسترسی داشته باشیم اما این کار را از طریق prop ها انجام می دهد (دقیقا مانند مثال ctr که شمارنده را مشخص می کرد) بنابراین صدا زدن آن به شکل بالا غلط بوده و حتما باید آن را از طریق props صدا بزنید.

 اگر یادتان باشد action ها فقط اطلاعات ارسالی به reducer هستند و خود به خود کاری انجام نخواهند داد. ما باید بر اساس type (مورد اجباری برای تمام action ها) مشخص کنیم که چه نوع action ای را دریافت کرده ایم تا به reducer بگوییم چه عملیاتی را انجام دهد. بنابراین در این مرحله باید به reducer.js برویم تا مشخص کنیم در هنگام دریافت action با تایپ 'INCREMENT' چه اتفاقی بیفتد:

const initialState = {
    counter: 0
}

const reducer = (state = initialState, action) => {
    if (action.type === 'INCREMENT') {
        return {
            counter: state.counter + 1
        }
    }
    return state;
};

export default reducer;

من با استفاده از یک شرط if بررسی کرده ام که اگر type ارسالی از action ما از نوع 'INCREMENT'  باشد می خواهیم state را تغییر دهیم. احتمالا برایتان جای سوال باشد: مگر نگفتیم که برای تغییر state باید همیشه immutable عمل کنیم؟ چرا در این قسمت state را با استفاده از اپراتور spread کپی نکرده ایم و مستقیما counter را تغییر داده ایم؟ پاسخ شما ساده است! از آنجایی که شیء state ما فقط یک key/value (همان counter) دارد، return کردن یک شیء جدید که دقیقا معادل شیء قبلی باشد آن را به طور کامل replace می کند و انگار کل شیء را کپی کرده و دوباره ارسال کرده ایم. تمام فلسفه کپی کردن state این بود که به جای تغییر قسمتی از آن، همه آن تغییر کند تا شاهد ناهمخوانی ها در قسمت های مختلف آن نباشیم و اگر همه شیء state فقط یک key/value است بنابراین با ارسال key/value جدید نیازی به کپی کردن نیست و تمام شیء replace خواهد شد.

حالا اگر دستور npm start را اجرا کرده و وارد مرورگر شوید، هیچ تغییر ظاهری مشاهده نخواهید کرد. دکمه های Add 5 و Subtract 5 و Decrement هنوز هم کار نمی کنند اما با کلیک روی Increment می بینید که یک واحد به شمارنده ما اضافه خواهد شد.

من از شما می خواهم که بقیه دکمه ها را نیز به همین شکل برنامه نویسی کنید و در جلسه بعد جواب من را با جواب خودتان مقایسه نمایید.

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

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