در قسمت قبل توانستیم متدی به نام addIngredientHandler بنویسیم که شکل زیر را داشت:
addIngredientHandler = (type) => { const oldCount = this.state.ingredients[type]; const updatedCount = oldCount + 1; const updatedIngredients = { ...this.state.ingredients }; updatedIngredients[type] = updatedCount; const priceAddition = INGREDIENT_PRICES[type]; const oldPrice = this.state.totalPrice; const newPrice = oldPrice + priceAddition; this.setState({ totalPrice: newPrice, ingredients: updatedIngredients }); }
روند اجرای این متد به صورت خلاصه بدین شکل است:
نتیجه این تابع به شکل زیر بود:
همانطور که می بینید با هر بار کلیک روی دکمه more توانسته ایم یک عدد دیگر از محتویات همبرگر را به آن اضافه کنیم. حالا نوبت آن است که متد زیر در فایل BurgerBuilder.js را تکمیل کنیم:
removeIngredientHandler = (type) => { }
قرار است این متد مسئول کم کردن محتویات باشد (دقیقا بر خلاف دکمه more). قسمت اول این تابع بسیار آسان است چرا که دقیقا مانند تابع addIngredientHandler است:
removeIngredientHandler = (type) => { const oldCount = this.state.ingredients[type]; const updatedCount = oldCount - 1; const updatedIngredients = { ...this.state.ingredients }; updatedIngredients[type] = updatedCount; }
در واقع تنها تفاوت این قسمت با تابع addIngredientHandler در این است که اینجا مقدار updatedCount را برابر یک واحد کمتر از oldCount قرار داده ایم (نه بیشتر). بقیه کدها تغییر State به شکل immutable است که در جلسات فصل قبل به آن پرداخته بودیم. حالا باید قسمت دوم این تابع یعنی محاسبه قیمت را تکمیل کنیم:
const priceDeduction = INGREDIENT_PRICES[type]; const oldPrice = this.state.totalPrice; const newPrice = oldPrice - priceDeduction; this.setState({ totalPrice: newPrice, ingredients: updatedIngredients });
منطق همان منطق تابع addIngredientHandler است اما نام متغیر priceAddition (به معنی اضافه قیمت) را به priceDeduction (تفریق قیمت) تغییر داده ایم. در آخر نیز قیمت محتویات جدید را از مقدار قبلی کم کرده ایم. حالا باید آن را به BuildControls پاس بدهیم بنابراین در همین فایل و در قسمت JSX می گوییم:
render() { return ( <Aux> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} /> </Aux> ); }
یعنی دقیقا کاری که با addIngredientHandler انجام دادیم. حالا به فایل BuildControls.js رفته و این تابع را به آن پاس بدهیم:
const buildControls = (props) => ( <div className={classes.BuildControls}> {controls.map(ctrl => ( <BuildControl key={ctrl.label} label={ctrl.label} added={() => props.ingredientAdded(ctrl.type)} removed={() => props.ingredientRemoved(ctrl.type)} /> ))} </div> );
توجه داشته باشید که ما نام هایی مانند added و removed را بر اساس سلیقه خودمان انتخاب کرده ایم و شما می توانید آن ها را تغییر دهید، اما حواستان باشد که موقع پاس دادن آن ها از نام های خودتان استفاده کرده باشید. حالا به فایل BuildControl.js بروید و روی دکمه Less از همان منطق More استفاده می کنیم:
const buildControl = (props) => ( <div className={classes.BuildControl}> <div className={classes.Label}>{props.label}</div> <button className={classes.Less} onClick={props.removed}>Less</button> <button className={classes.More} onClick={props.added}>More</button> </div> );
حالا فایل هایتان را ذخیره کنید و به مرورگر بروید تا این کدها را امتحان کنیم. من چند بار دکمه More را می زنم تا چند گوشت و پنیر و... به همبرگر اضافه شوند:
حالا چند بار دکمه Remove را می زنم و می بینم که محتویات ما به آسانی حذف می شوند:
اما این کد یک اشکال بزرگ دارد. آیا می توانید آن را حدس بزنید؟
بله اگر عنصری که نداریم را Less بزنیم با خطا مواجه می شویم. دلیلش هم این است که ما سعی کرده ایم محتویاتی را حذف کنیم که اصلا وجود نداشته اند و state مقادیر منفی می گیرد (مثلا پنیر می شود 1- و...) و ما هم نمی توانیم از مقادیر منفی یک آرایه بگیریم و آن را render کنیم. بنابراین کدهای removeIngredientHandler نیاز به کار بیشتری دارند.
ابتدا باید چک کنیم که آیا عناصری که درخواست حذف آن ها ارسال شده است اصلا وجود دارند یا خیر؛ به زبان ساده تر متغیر oldCount در این تابع باید بیشتر از صفر باشد. بنابراین:
removeIngredientHandler = (type) => { const oldCount = this.state.ingredients[type]; if (oldCount <= 0) { return; } const updatedCount = oldCount - 1; const updatedIngredients = { ...this.state.ingredients }; updatedIngredients[type] = updatedCount; const priceDeduction = INGREDIENT_PRICES[type]; const oldPrice = this.state.totalPrice; const newPrice = oldPrice - priceDeduction; this.setState({ totalPrice: newPrice, ingredients: updatedIngredients }); }
شرط if ما می گوید اگر مقدار oldCount از صفر کمتر بود (منفی بود) فقط return کن. ما هیچ چیزی را return نکرده ایم بنابراین خود return به تنهایی هیچ کاری انجام نمی دهد و در کل اگر کاربر بخواهید مقداری که صفر است را Less کند هیچ اتفاقی نمی افتد. شما می توانید فایل هایتان را ذخیره کرده و به مرورگر بروید. هر چقدر هم روی دکمه Less کلیک کنید (در صورتی که محتویات صفر باشد) هیچ اتفاقی نخواهد افتاد.
گرچه این روش کار می کند اما روش قشنگی نیست، بلکه بهتر است دکمه را disable کنیم. برای این کار در همین فایل به قسمت ()Render بروید و قبل از return شدن JSX یک ثابت به نام disabledInfo ایجاد کرده و مجتویات state (قسمت ingredient) را در آن کپی می کنیم:
const disabledInfo = { ...this.state.ingredients };
یعنی در شروع disabledInfo به شکل زیر است:
{ salad: 0, bacon: 0, cheese: 0, meat: 0 }
یک شیء ساده جاوا اسکریپتی که مقادیر state را درون خود کپی کرده است.
حالا با استفاده از یک حلقه for می گوییم:
for (let key in disabledInfo) { disabledInfo[key] = disabledInfo[key] <= 0 }
برای key ها (مثل salad و bacon و meat) یک بررسی انجام داده ایم. اگر قسمت راست این بررسی صحیح باشد (یعنی key ها مقداری مساوی یا کمتر از صفر داشته باشند) نتیجه true می شود بنابراین disabledinfo برای آن key خاص برابر با true می شود. یعنی دیگر key و value های ما به شکل bacon: 2 و ... نیست بلکه به طور مثال ممکن است به شکل زیر در بیاید:
{ salad: true, bacon: false, // و ... }
بنابراین می خواهیم اگر مقدار خصوصیتی true بود، دکمه اش غیرفعال شود. حالا باید در قسمت return آن را پاس بدهیم:
return ( <Aux> <Burger ingredients={this.state.ingredients} /> <BuildControls ingredientAdded={this.addIngredientHandler} ingredientRemoved={this.removeIngredientHandler} disabled = {disabledInfo} /> </Aux> );
با این کار این prop را به BuildControls.js پاس داده ایم بنابراین به آن فایل می رویم تا دکمه را غیرفعال کنیم:
const buildControls = (props) => ( <div className={classes.BuildControls}> {controls.map(ctrl => ( <BuildControl key={ctrl.label} label={ctrl.label} added={() => props.ingredientAdded(ctrl.type)} removed={() => props.ingredientRemoved(ctrl.type)} disabled={props.disabled[ctrl.type]} /> ))} </div> );
حواستان باشد که قسمت ctrl.type را فراموش نکنید. این قسمت مشخص می کند که مقدار کدام key را می خواهیم. حالا به فایل BuildControl.js می رویم و می گوییم:
const buildControl = (props) => ( <div className={classes.BuildControl}> <div className={classes.Label}>{props.label}</div> <button className={classes.Less} onClick={props.removed} disabled={props.disabled}>Less</button> <button className={classes.More} onClick={props.added}>More</button> </div> );
باید بدانید که disabled یک attribute در زبان HTML است بنابراین اینجا منظورمان از disabled پاس داده شده به دکمه Less همان attribute زبان HTML است که مقدار آن (از props.disabled) یا true می شود و یا false؛ اگر true بشود دکمه ما غیرفعال خواهد شد.
حالا اگر فایل ها را ذخیره کرده و به مرورگر بروید می بینید که دکمه Less غیرفعال شده و فقط زمانی فعال می شود که حداقل یک یا تعداد بیشتری از ingredient خاصی را داشته باشیم:
امیدوارم از این قسمت لذت برده باشید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.