در قسمت قبل توانستیم به سادگی دکمه Increment را کدنویسی کنیم و از شما خواستم که خودتان دکمه های دیگر را نیز تکمیل کنید. قبل از شروع بحث این جلسه می خواهم پاسخ سوال جلسه قبل خودم را برایتان بگویم. در ابتدا باید قسمت mapDispatchToProps کدهای action های دیگر را اضافه می کردید:
const mapDispatchToProps = dispatch => { return { onIncrementCounter: () => dispatch({type: 'INCREMENT'}), onDecrementCounter: () => dispatch({type: 'DECREMENT'}), onAddCounter: () => dispatch({type: 'ADD'}), onSubtractCounter: () => dispatch({type: 'SUBTRACT'}) }; };
سپس باید به قسمت JSX رفته و این توابع را فراخوانی می کردید:
render() { return ( <div> <CounterOutput value={this.props.ctr} /> <CounterControl label="Increment" clicked={this.props.onIncrementCounter} /> <CounterControl label="Decrement" clicked={this.props.onDecrementCounter} /> <CounterControl label="Add 5" clicked={this.props.onAddCounter} /> <CounterControl label="Subtract 5" clicked={this.props.onSubtractCounter} /> </div> ); }
در نهایت در reducer با شرط های if مختلف انواع action.type ها را چک می کردید:
const reducer = (state = initialState, action) => { if (action.type === 'INCREMENT') { return { counter: state.counter + 1 } } if (action.type === 'DECREMENT') { return { counter: state.counter - 1 } } if (action.type === 'ADD') { return { counter: state.counter + 10 } } if (action.type === 'SUBTRACT') { return { counter: state.counter - 8 } } return state; };
من در اینجا عدد های مختلفی را قرار داده ام (مثلا برای SUBTRACT منهای 8 را گذاشته ام) تا شما بدانید انتخاب این اعداد با شما است و می توانید هر مقداری را در آن ها قرار دهید. این پاسخ سوال جلسه قبل من بود، امیدوارم که پاسخ شما هم مثل من بوده باشد.
حالا سوال دیگری برای شما دارم: به نظر شما آیا نحوه نوشتن کدهای بالا بهینه است؟!
در حال حاضر ما میزان کم یا زیاد شدن شمارنده را به صورت دستی درون reducer تعیین کرده ایم اما برخی اوقات نیاز است که مقادیر را از سمت کاربر بگیریم. مثلا هنگامی که کاربر می خواهد پست جدیدی را در سایت ما بنویسد، خودش مقادیر مختلف (مثل عنوان مطلب) را تعیین خواهد کرد. بنابراین به جای اینکه مقادیر را به صورت دستی در reducer وارد کنیم باید با استفاده از روش خاصی، اطلاعاتی را همراه با action ارسال کنیم تا در reducer بنشیند.
انجام این کار بسیار آسان است، ابتدا به mapDispatchToProps می رویم و هر مقداری را که خواستیم با هر نامی که خواستیم به آن پاس می دهیم. مثلا فرض کنید من می خواهم دکمه Add همیشه 10 واحد اضافه کرده و دکمه Subtract همیشه 15 واحد کم کند. در این صورت می توان گفت:
const mapDispatchToProps = dispatch => { return { onIncrementCounter: () => dispatch({ type: 'INCREMENT' }), onDecrementCounter: () => dispatch({ type: 'DECREMENT' }), onAddCounter: () => dispatch({ type: 'ADD', val: 10 }), onSubtractCounter: () => dispatch({ type: 'SUBTRACT', val: 15 }) }; };
یادتان باشد که dispatch فقط یک شیء ساده می گیرد، بنابراین می توانید هر نوع اطلاعاتی که خواستید درون آن وارد کنید. به طور مثال می توانید چندین key دیگر علاوه بر val داشته باشید یا یک key دیگر داشته باشید که خودش یک شیء باشد و الی آخر. همچنین باید توجه کنید که type تنها key این تابع است که نام آن اجباری و غیر قابل تغییر می باشد بنابراین شما می توانید به جای val (که انتخاب من بوده است) هر نام دیگری را انتخاب کنید، مثلا value یا payload یا amount و الی آخر.
حالا می توانیم به reducer رفته و به جای مشخص کردن مقدار به صورت دستی، از همین خصوصیت val استفاده کنیم:
const reducer = (state = initialState, action) => { if (action.type === 'INCREMENT') { return { counter: state.counter + 1 } } if (action.type === 'DECREMENT') { return { counter: state.counter - 1 } } if (action.type === 'ADD') { return { counter: state.counter + action.val } } if (action.type === 'SUBTRACT') { return { counter: state.counter - action.val } } return state; };
اگر شما به جای val نام دیگری مثل amount را انتخاب کردید اینجا هم باید action.amount را وارد کرده باشید. قبل از اینکه به مرورگر برویم باید نام دکمه Subtract را تصحیح کنیم چرا که دیگر 10 واحد کم نمی کند بلکه 15 واحد کم می کند. همچنین دکمه Add 5 به جای 5 واحد 10 واحد اضافه می کند:
render() { return ( <div> <CounterOutput value={this.props.ctr} /> <CounterControl label="Increment" clicked={this.props.onIncrementCounter} /> <CounterControl label="Decrement" clicked={this.props.onDecrementCounter} /> <CounterControl label="Add 10" clicked={this.props.onAddCounter} /> <CounterControl label="Subtract 15" clicked={this.props.onSubtractCounter} /> </div> ); }
با تغییر label این دکمه ها مشکل ما حل می شود و حالا برنامه به صورت 100 درصد کار می کند.
حالا باز هم از شما سوال دارم: آیا کدهای ما در بهینه ترین حالت خود است؟
پاسخ قطعا خیر است! همانطور که می دانید شرط های if زمانی کاربرد دارند که بخواهیم از بین دو یا چند حالت معدود (معمولا کمتر از 3 شرط) یک حالت را انتخاب کنیم یا برای حالت های مختلف کد بنویسیم. این مسئله یک رسم بین برنامه نویسان است و قانونی اجباری نیست بنابراین شما می توانید از 100 شرط if پشت سر هم نیز استفاده کنید اما از نظر برنامه نویسی کدهای شلوغی خواهید داشت و این کدها ظاهری زننده به خود می گیرند. به محتویات فایل reducer نگاه کنید:
const reducer = (state = initialState, action) => { if (action.type === 'INCREMENT') { return { counter: state.counter + 1 } } if (action.type === 'DECREMENT') { return { counter: state.counter - 1 } } if (action.type === 'ADD') { return { counter: state.counter + action.val } } if (action.type === 'SUBTRACT') { return { counter: state.counter - action.val } } return state; };
این فایل پر از شرط های if است اما اگر چنین وضعیتی داشته باشیم که در آن بیشتر از 2 یا 3 شرط داریم بهتر است از همان دستور switch استفاده کنیم چرا که دستور switch برای چنین مواقعی ساخته شده است:
const reducer = (state = initialState, action) => { switch (action.type) { case 'INCREMENT': return { counter: state.counter + 1 } case 'DECREMENT': return { counter: state.counter - 1 } case 'ADD': return { counter: state.counter + action.val } case 'SUBTRACT': return { counter: state.counter - action.val } } return state; };
در یک نگاه ساده می توان فهمید که با این کار کد ما چقدر تمیزتر و خواناتر شده است. در این کد مشخص است که به دنبال action.type هستیم و هر case را جداگانه بررسی می کنیم. همچنین نیازی به دستور break نیست چرا که دستور return هر جا اجرا شود، از آن حلقه و دستور به طور کامل خارج می شود بنابراین break را درون خودش دارد. حتی اگر هیچ کدام از case های ما برابر action.type نباشد نیز حالت پیش فرض return state را در انتهای این کد داریم تا کدهایمان روی هوا نماند! باید به یاد داشته باشید که هر action ای که ارسال شود باید به reducer برود بنابراین داشتن حالت پیش فرض return state کمک بزرگی است.
در قسمت بعد در رابطه با تغییر state به صورت immutable بیشتر صحبت خواهیم کرد چرا که می خواهم نکات جالبی را برایتان توضیح بدهم
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.