راهنمای مقدماتی ریداکس (Redux) – به زبان ساده


درک Redux به عنوان یک مبتدی میتواند تا حدودی دشوار باشد. ریداکس مفاهیم و اصطلاحات جدیدی زیادی دارد که در اغلب موارد چندان سر راست نیستند. در این راهنما یک مثال بسیار ساده از پیادهسازی ریداکس را بررسی میکنیم و هر یک از مراحل و اصطلاحات را طوری تعریف میکنیم که برای افراد کاملاً مبتدی معنیدار باشد.
ما قصد داریم این یک راهنما برای آشنایی کامل با اجزای ریداکس باشد. در این مقاله تعاریف کاملاً فنی نیامده است. همچنین شامل بهترین رویهها نیز نمیشود. این راهنما صرفاً تعاریف مقدماتی را شامل میشود که به ایجاد درکی از ریداکس برای افرادی که سابقه آشنایی قبلی با آن ندارند کمک میکند. در واقع این یک پیادهسازی ساده است و جزییات غیرضروری در آن ارائه نشدهاند.
در مثال ارائه شده در این راهنما یک اپلیکیشن ToDo ساده خواهیم ساخت. این اپلیکیشن امکان افزودن و حذف موارد ToDo و نمایش آنها را در یک صفحه به ما میدهد.
در این راهنما هر یک از اجزای ریداکس را به صورت مرحله به مرحله بررسی میکنیم و کاری که آن جزء انجام میدهد و شیوه پیادهسازی آن را با کدهای نمونه معرفی خواهیم کرد. در انتهای این مقاله کد کامل مثالی که معرفی میکنیم را مشاهده خواهید کرد. این اپلیکیشن میتواند به عنوان یک اپلیکیشن کامل React عمل کند.
فهرست مطالب
- نوشتن تابع reducer
- وهلهسازی store در مؤلفه ریشه
- قرار دادن کامپوننتها در کامپوننت <Provider> و ارسال در store به عنوان یک prop
- نوشتن کامپوننت
- تعریف اکشنها
- تعریف dispatch و الصاق این موارد به dispatch-هایی که تحریک خواهند کرد. (در شنونده رویداد و غیره)
- تعریف تابع mapStateToProps
- اکسپورت کردن تابع اتصال، ارسال mapStateToProps و null به عنوان دو آرگومان و ارسال نام کامپوننت در پرانتز دوم
در ادامه همه این مراحل را به یک به یک تشریح میکنیم.
1. نوشتن تابع Reducer
تابع reducer تابعی است که شیوه پاسخدهی store به اکشنها (actions) را تعیین میکند. این تابع وضعیت جدید و بهروز شده را هر زمان که اکشنی dispatch میشود بازگشت میدهد. State تغییرناپذیر (immutable) است و از این رو reducer همواره یک state جدید بازگشت میدهد. reducer معمولاً از عملگر spread برای درج حالت در یک شیء یا آرایه جدید و الصاق به آن استفاده میکند. رویه معمول استفاده از گزاره switch/case و بررسی نوع مشخصه اکشن ارسال شده است. سپس کدی که باید حالت را برای هر مورد بهروزرسانی کند مینویسیم.
ما تابع reducer خود را ابتدا مینویسیم، زیرا باید آن را هنگام وهلهسازی از store ارسال کنیم. برای درک این که چه اتفاقاتی در جریان است، باید درکی از اکشنها و همچنین dispatch داشته باشید. ما این موارد را در ادامه راهنما بیشتر توضیح خواهیم داد.
فعلاً همین قدر بدانید که اپلیکیشن ToDo ما باید به 2 روش با Store تعامل داشته باشد: یکی این که یک آیتم todo را به حالت اضافه میکند و دوم این که آیتم todo را از حالت حذف میکند. از این رو تابع خود را چنان مینویسیم که به 2 حالت بر حسب نوع اکشن پاسخ دهد. این تابع از مقدار اکشن برای افزودن یا حذف کردن آیتم todo از حالت استفاده میکند.
تابع reducer دو پارامتر ارسال میکند که یکی حالت (state) است که همان حالت کلی موجود در store است و در صورتی که حالت موجود نباشد، مقدار پیشفرض برای آن تعیین میکنیم و دیگری اکشن است. ما حالت را در وضعیت پیشفرض بازگشت میدهیم:

2. وهلهسازی از store در کامپوننت ریشه
Store آن چیزی است که عملاً حالت در آن قرار میگیرد. این شیء کمی عجیب است و عملاً لازم نیست روش ورود و خروج حالت به آن را بدانید. تنها چیزی که در این مرحله باید بدانیم این است که دسترسی مستقیمی به آن نداریم و از این جهت به حالت معمولی در React شباهت ندارد. ما با استفاده از reducer، اکشن و dispatch به آن دسترسی مییابیم.
نکته مهم دیگر که باید در مورد store بدانید این است که store برخی متدهای مفید و مهم دارد. متد اصلی تابع dispatch است همچنین شامل یک متد getState برای مشاهده حالت و متد subscribe برای اجرای callback در زمان dispatch شدن اکشنها است.
Store به طور معمول در ریشه اپلیکیشن (یعنی فایل App.js) وهلهسازی میشود و به صورت یک متغیر ذخیره شده و موجب میشود که reducer به صورت یک پارامتر به آن ارسال شود. سپس store به صورت یک prop به کامپوننت Provider ارسال میشود.
ما یک وهله از شیء store خود میسازیم و آن را به reducer ی که پیشتر نوشتیم ارسال میکنیم.

3. قرار دادن کامپوننتها در <Provider> و ارسال به store به صورت prop
Provider کامپوننتی است که جهت تسهیل ارسال store به همه کامپوننتهای دیگر ساخته شده است. کامپوننت Provider همه کامپوننتهای دیگر را شامل میشود. به عبارت دیگر همه کامپوننتها به عنوان فرزندان Provider رندر میشوند. ما store را در یک prop به Provider ارسال میکنیم. این بدان معنی است که لازم نیست آن را به صورت یک prop هر بار به کامپوننتهای مختلف ارسال کنید، زیرا همه آنها store را از Provider دریافت میکنند. با این وجود، این بدان معنی نیست که کامپوننتها به حالت دسترسی دارند. ما همچنان باید از mapStateToProps برای ایجاد امکان دسترسی در کامپوننت به حالت استفاده کنیم.
ما کامپوننت Todo را در Provider قرار میدهیم. به این منظور آن را در store که در مرحله قبل ساختیم ارسال میکنیم.

4. نوشتن کامپوننت
در این مرحله شروع به نوشتن کامپوننت Todo میکنیم که آیتمهای todo را رندر کرده و با stroe ریداکس در تعامل خواهد بود.
این کامپوننت، یک کامپوننت مفید است که شامل یک عنصر حالت برای نگهداری در مواردی که کاربر چیزی در ورودی تایپ کرده میشود. ما یک تابع به نام handleChange داریم. این تابع هر بار که کاربر چیزی را در ورودی تایپ میکند بهروز میشود. تا به این جا، این همه آن چیزی است که نوشتهایم. ما باید پیش از نوشتن موارد بیشتر در خصوص منطق اپلیکیشن، اطلاعات بیشتری درباره ریداکس کسب کنیم. منطق اپلیکیشن ما باعث میشود todo های جدید به حالت اضافه شوند و آیتمهای موجود را از حالت بازیابی کرده و روی صفحه نمایش دهیم.

5. تعریف اکشنها
یک اکشن صرفاً شیئی است که حاوی مشخصاتی به نام type است. این شیء به تابع dispatch ارسال میشود. از آن برای بیان رویدادهایی که رخ داده به store استفاده میشود. همچنین تعیین میکند که چه بهروزرسانیهایی باید در پاسخ در حالت (state) رخ دهند. اکشن میتواند شامل مشخصات دیگری نیز برای دادههای مختلفی که باید به reducer ارسال شوند باشد. دادهها تنها میتوانند از این طریق ارسال شوند و از این رو هر دادهای که باید ارسال شود باید از این طریق ارسال شود.
ما از ایجادکنندههای اکشن برای تعریف آنها استفاده میکنیم. ایجادکنندههای اکشن تابعهایی هستند که شیء اکشن را بازگشت میدهند. هدف از این تابعها این است که اکشنها قابلیت پرتابل و تست شدن بیشتری بیایند. این وضعیت رفتار کلی انجام کارها را تغییر نمیدهد؛ بلکه یک روش دیگر برای نوشتن و ارسال اکشن محسوب میشود. همچنین امکان ارسال پارامترها در صورت نیاز به ارسال دادههایی به همراه اکشن را فراهم میسازد. بنابراین به استفاده از ایجادکنندههای اکشن نیازمند هستیم.
اگر به خاطر داشته باشید reducer ما با 2 نوع اکشن پاسخ میداد که شامل ADD_TODO و REMOVE_TODO بود. ما این اکشنها را به وسیله ایجادکنندههای اکشن تعریف خواهیم کرد. در اکشن add_todo عبارت «ADD_TODO» را به عنوان نوع و آیتم todo را که میخواهیم به store اضافه شود بازگشت میدهیم. در remove_todo نیز «REMOVE_TODO» را به عنوان نوع و اندیس آیتم todo را در store به عنوان مقدار بازگشت میدهیم. بدین ترتیب باید آیتم را از فهرست todo ها حذف کنیم.

اگر به تعریف تابع reducer بازگردیم، اینک آن را بهتر درک میکنیم. reducer با خواندن action.type میداند که باید todo را به store اضافه یا حذف کند. همچنین آیتم todo را که در add_todo ارسال شده دریافت و با استفاده از عملگر spread به state کنونی اضافه میکند. در remove_todo از عملگر spread برای ایجاد یک آرایه جدید جهت الحاق (دو بار) به حالت کنونی استفاده میشود، یک بار با همه عناصر پیش از حذف و بار دیگر با همه عناصر پس از حذف. از این رو شیء حالت جدید با آیتم حذف شده ایجاد میشود.

با این وجود، همچنان کار ما پایان نیافته است. ما هنوز به بررسی چگونگی فراخوانی شدن reducer و ارسال اکشن صحیح نپرداختهایم. به این منظور باید به تعریف تابع dispatch بپردازیم.
6. تعریف Dispatch
تابع dispatch یک روش store است که برای تحریک تغییرات در حالت استفاده میشود. هر رویداد یا هر چیزی که باید در زمان تغییر حالت، بهروزرسانی شود، باید متد dispatch را به این منظور فراخوانی کرد. این تنها روش تحریک تغییر/بهروزرسانی برای حالت است، یعنی dispatch فراخوانی میشود و شیء اکشن به آن ارسال میشود. زمانی که dispatch تحریک میشود، متعاقباً store تابع reducer را فراخوانی میکند و اکشنی که dispatch برای بهروزرسانی حالت ارائه کرده و قبلاً دیدیم را ارسال میکند.
در ادامه نیمه دوم کد خودمان، متد رندر کامپوننتها را تعریف میکنیم. سپس دکمههایی که شنوندههای رویداد ما را اجرا میکنند را تعریف میکنیم و درون آنها تابعهای dispatch را قرار میدهیم.
نخستین دکمه یک دکمه افزودن (add) ساده است. این دکمه اکشن add_todo را به store میفرستد. بدین ترتیب ورودی فعلی کاربر به عنوان مقدار ارسال میشود. دقت کنید که ما dispatch را به صورت this.props.dispatch ارسال میکنیم. درک چگونگی و چرایی ارسال به صورت یک prop به کامپوننت خارج از حیطه این مقاله است. بنابراین کافی است بدانیم که این تابع چه کار میکند و بدین ترتیب آن را فراخوانی کنیم.
شنونده رویداد دوم که مینویسیم یک متد onClick روی آیتم todo رندر شده است. با کلیک کردن روی هر آیتم todo در صفحه، یک شنونده رویداد تحریک میشود. این شنونده رویداد به دنبال لیستی از todo ها میگردد و اندیسی را که todo در لیست دارد پیدا میکند. سپس اکشن remove_todo را dispatches کرده و اندیس آن را ارسال میکند.

چرخه شیوه بهروزرسانی حالت در stroe ریداکس اینک به طور کامل تعریف شده است. میدانیم که هر زمان بخواهیم حالت را تغییر دهیم باید متد dispatch را فراخوانی کنیم و اکشن مناسب را ارسال کرده و مطمئن شویم که reducer این اکشنها را مدیریت کرده و حالت جدید را با استفاده از مقادیری که از طریق اکشن ارسال کردهایم بازگشت میدهد.
تنها نکته مغفول در این چرخه شیوه دریافت حالت از stroe ریداکس است. قبلاً متوجه شدیم که یک لیست به نام this.props.todos را نگاشت نمیکنیم. شاید کنجکاو شده باشید که این لیست از کجا آمده است. اگر به خاطر داشته باشید در ابتدای این مقاله اعلام کردیم که ارسال store به کامپوننت Provider برای کسب دسترسی به حالت در store کفایت نمیکند. همه این موارد در 2 مرحله بعدی که تابع mapStateToProps را تعریف کرده و آن را به تابع connect ارسال میکنیم انجام مییابند.
7. تعریف تابع mapStateToProps
زمانی که بخواهیم کامپوننت ما به حالت دسترسی داشته باشد، باید صریحاً تعریف کنیم که کامپوننتها چه چیزی در حالت دسترسی خواهد داشت. کامپوننت بدون این تعریف به چیزی در حالت دسترسی نخواهد داشت.
mapStateToProps تابعی است که صرفاً یک شیء بازگشت میدهد که در آن مقادیر مورد نیاز برای دسترسی تعریف شدهاند. در واقع شیئی که در mapStateToProps بازگشت مییابد همان prop ها در کامپوننت ما هستند. تابع mapStateToProps به عنوان آرگومان نخست به متد connect ارسال میشود.
تابع mapStateToProps کل حالت را به عنوان یک پارامتر میگیرد و تنها آن چیزی را که مورد نیاز است از آن برمیدارد. در ادامه حالت ما را مشاهده میکنید که تنها شامل لیستی از todo-ها است. ما باید این لیست را در کامپوننت ToDo خود داشته باشیم و بنابراین کل حالت را به عنوان یک خصوصیت که todos مینامیم بازگشت میدهیم.

همان طور که شاهد هستید، ما اینک به همه todo ها در props خود به صورت this.props.todos دسترسی داریم. بدین ترتیب ما میتوانیم همه todo ها را در مثال قبلی با نگاشت کردن روی آن رندر کنیم.
در نهایت باید این تابع را به متد connect خود ارسال کنیم تا همه چیز به هم متصل شود.
8. اکسپورت کردن تابع Connect
Connect متدی است که به تابعهای mapStateToProps و mapDispatchToProps در کامپوننت قلاب میشود به طوری که store میتواند این تابعها را خوانده و مطمئن شود که آن چه در آنها تعریف کردهاید به صورت props به کامپوننت ارسال میشوند. این متد یک ساختار خاص دارد که به صورت زیر است:
connect(mapStateToProps, MapDispatchToProps)(YourComponent)
ما 2 تابع mapStateToProps و mapDispatchToProps را به connect ارسال میکنیم و سپس نام کامپوننت را درون پرانتز دوم میفرستیم. یک الگوی رایج این است که متد connect را به جای کامپوننت در زمان اکسپورت کردن کامپوننت در انتهای فایل، اکسپورت کنیم. برای نمونه به صورت زیر عمل میکنیم:
export default connect(mapStateToProps, MapDispatchToProps)(YourComponent)
سپس به روش مشابه به طور معمول چنان که حالت از ما انتظار دارد، اکسپورت میکنیم و dispatch-ها در prop ها ارسال میشوند. تابعهای mapStateToProps و mapDispatchToProp در واقع پارامترهای اختیاری برای connect هستند. اگر بخواهید میتوانید یک یا دو مورد از آنها را ارسال نکنید و به جایش آنها را pull کنید.
شاید کنجکاو شده باشید که این تابع mapDispatchToProps از کجا میآید و چرا تاکنون به آن اشاره نکردهایم. از آنجا که این مقاله یک راهنمای کاملاً ساده از store ریداکس است و mapDispatchToProps عملاً اجباری نیست آن را در مثال خود نگنجاندهایم. اگر mapDispatchToProps را ارسال نکنید و به جای آن مقدار null بفرستید، همچنان میتوانید به تابع dispatch در کامپوننت دسترسی داشته باشید، چنان که قبلاً در this.props.dispatch این دسترسی را داشتیم.
بدین ترتیب برای این که اپلیکیشن خود را کامل کنیم تنها کاری که باید انجام دهیم این است که کامپوننت خود را در حالی که درون تابع connect پیچیده شده و mapStateToProps را چنان که قبلاً تعریف کردیم ارسال میکند اکسپورت نماییم.

به این ترتیب کامپوننت تکمیل شده است. این یک پیادهسازی کامل از store ریداکس است. در ادامه مثال عملی آن چه پیادهسازی کردیم را شاهد هستید:
کد کامل مثالی که در این نوشته بررسی کردیم را به روش حاشیهنویسی شده در ادامه مشاهده میکنید:
فایل App.js
فایل Todo.js
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای طراحی و توسعه پروژه های وب
- آموزش جاوا اسکریپت (JavaScript)
- مجموعه آموزشهای طراحی و برنامه نویسی وب
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
- آموزش JavaScript ES6 (جاوااسکریپت)
- 1۰ کتابخانه و فریمورک جاوا اسکریپت که باید آنها را بشناسید
==
خوب بود. ممنون
ردوکس چیه دیگه!!
ریداکس