در این قسمت و چند قسمت آینده می خواهیم مباحث redux پایه را روی پروژه همبرگر ساز خود پیاده کنیم تا با کارایی عملی آن آشنا شویم اما بر اساس گفته های جلسه قبل بهتر است ابتدا برنامه را آنالیز کنیم تا متوجه بشویم چه قسمت هایی از برنامه ما نیاز به redux دارند.
ما پروژه همبرگرساز را طوری طراحی کرده ایم که تمام state درون container ها مدیریت می شود و نیازی به بررسی component ها مثل Burger و Logo و ... نداریم. اول از همه با BurgerBuilder.js شروع می کنیم:
state = { ingredients: null, totalPrice: 4, purchasable: false, purchasing: false, loading: false, error: false }
اگر به این State نگاه کنیم، متوجه می شویم که علاوه بر محتویات و قیمت همبرگر، چند state دیگر داریم که مربوط به وضعیت خرید همبرگر هستند. به طور مثال purchasing برای نمایش Modal خرید استفاده می شد:
return ( <Aux> <Modal show={this.state.purchasing} modalClosed={this.purchaseCancelHandler}> {orderSummary} </Modal> {burger} </Aux> );
یا به طور مثال loading برای نمایش spinner (علامت loading) و error نیز برای نمایش خطا و محتوای متفاوت استفاده می شوند. بنابراین می توان گفت که purchasing و loading و error جزء Local UI State هستند که در جلسه قبل در موردشان بحث کردیم و همانطور که آنجا گفتم نیازی به واگذاری آن ها به redux نمی بینم، بلکه بیشتر روی محتویات همبرگر و قیمت آن تمرکز می کنیم تا با redux مدیریت شوند.
در نهایت فقط purchasable می ماند که آن هم برای قفل کردن یا فعال کردن دکمه ORDER و ثبت سفارش استفاده می شود (یعنی جزء موارد UI است) اما می توانیم بگوییم مقداری که دریافت می کند و نحوه عملکرد آن به همبرگر ساخته شده توسط کاربر بستگی دارد بنابراین شاید بهتر باشد که آن را از طریق redux مدیریت کنیم. با این اوصاف هنوز مشخص نیست که با purchasable چه کار خواهیم کرد و در روند کار تصمیم خواهیم گرفت.
در مرحله بعد باید Checkout.js را بررسی کنیم:
state = { ingredients: null, price: 0 }
باز هم محتوای همبرگر (ingredients) و قیمت آن (price) را داریم. این مسئله اهمیت استفاده از redux را به ما نشان می دهد تا اینگونه state های تکراری در سر تا سر برنامه نداشته باشیم. همچنین در حال حاضر مجبور هستیم این مقادیر را از طریق query param ها ارسال کنیم که اصلا ظاهر جالبی ندارد:
componentWillMount () { const query = new URLSearchParams( this.props.location.search ); const ingredients = {}; let price = 0; for ( let param of query.entries() ) { // ['salad', '1'] if (param[0] === 'price') { price = param[1]; } else { ingredients[param[0]] = +param[1]; } } this.setState( { ingredients: ingredients, totalPrice: price } ); }
همچنین مشکل مربوط به Route ما را نیز حل می کند. در حال حاضر ما یکی از route ها را به شکل عجیب زیر نوشته ایم تا بتوانیم محتویات همبرگر را به ContacData ارسال کنیم:
render={(props) => (<ContactData ingredients={this.state.ingredients} price={this.state.totalPrice} {...props} />)} />
در فایل ContactData.js نیز تقریبا تمام state مربوط به UI است و برای ما اهمیت ندارد اما در Order.js مقداری به نام orders را داریم که داده ها را از سرور دریافت می کند. این مورد را فعلا توضیح نمی دهم تا بعدا با اجرای کدهای نامتقارن در redux 2 آشنا شویم. حالا باید عملا وارد کار شویم...
برای شروع یک ترمینال درون پوشه همبرگر ساز خود باز کرده یا از ترمینال خود VSCode استفاده کرده و دستور زیر را اجرا کنید:
npm install --save redux react-redux
با اضافه کردن یک اسپیس بین نام دو پکیج redux و react-redux می توانیم با یک دستور هر دو پکیج را همزمان متصل کنیم. حالا که هر دو پکیج نصب شده اند باید Store و موارد اولیه را ایجاد کنیم. برای این کار درون پوشه src یک پوشه جدید به نام store ایجاد کنید که حاوی دو فایل reducer.js و actions.js باشد.
اگر به فایل BurgerBuilder.js نگاهی بیندازیم، متدهایی را که تعریف کرده ایم مشاهده خواهیم کرد. ما یک addIngredientHandler و یک removeIngredientHandler داریم که دو متد اصلی ما هستند و می توانند به صورت action باشند. بنابراین به فایل actions.js در پوشه store بروید و این دو مورد را به آن اضافه کنید:
export const ADD_INGREDIENT = 'ADD_INGREDIENT'; export const REMOVE_INGREDIENT = 'REMOVE_INGREDIENT';
بهتر است فعلا store خود را با همین دو مورد ایجاد کنیم و سپس در قسمت های بعد کدها را تکمیل می کنیم.
وارد فایل reducer.js شوید و این دو action را import کنید:
import * as actionTypes from './actions';
با استفاده از دستور بالا تمام action ها را import کرده ایم حتی اگر در آینده action های بیشتری اضافه کنیم. سپس یک initialState یا state اولیه تعریف می کنیم تا نشان دهیم در ابتدا state ما چه حالتی داشته باشد:
import * as actionTypes from './actions'; const initialState = { ingredients: null, totalPrice: 4 };
من فعلا از همان state درون burgerbuilder استفاده کرده ام. حالا می توانیم store خود را ایجاد کنیم:
import * as actionTypes from './actions'; const initialState = { ingredients: null, totalPrice: 4 }; const reducer = (state = initialState, action) => { }; export default reducer;
من از توابع ES6 برای تعریف reducer استفاده کرده ام و مقدار state را برابر initialState گذاشته ام. چرا؟ قبلا گفته بودیم که دفعه اولی که برنامه اجرا می شود، State برابر با undefined خواهد بود و با تعریف state اولیه از بروز چنین خطایی جلوگیری می کنیم. من کدهای درون reducer را در جلسات آینده تکمیل خواهم کرد، فعلا به سراغ مدیریت state درون فایل index.js می رویم:
const app = ( <BrowserRouter> <App /> </BrowserRouter> );
همانطور که می بینید ما در حال حاضر برنامه خودمان را درون BrowserRouter گذاشته ایم. به نظر شما در چنین حالتی provider را در کدام قسمت قرار دهیم؟ آیا آن را درون BrowserRouter قرار دهیم یا خارج از آن؟
در قسمت بعد پاسخ این سوال را پیدا خواهید کرد.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.