بهبود Reducer–ها با استفاده از React و Typescript – راهنمای کاربردی


بهرهگیری از Reducer-های حالت و تغییرناپذیری (immutability) در مقیاس بالا ممکن است کار دشواری باشد. علیرغم همه ابزارهایی که برای مدیریت این وضعیت داریم، در نهایت محدود به یک الگوی منفرد هستیم که رشد اپلیکیشن ما را تعیین میکند. بدین ترتیب حالت اپلیکیشن چیزی بیش از یک چالش محسوب میشود و داستانی است که هر بار به سراغ کدنویسی میرویم با آن مواجه میشویم. از همین رو بهبود Reducer-ها از اهمیت بالایی برخوردار است.
زمانی که با React سر و کار داریم، برخی اوقات شیوه تعریف کردن حالتهای مختلف هیچ اهمیتی ندارد بلکه روش اتصال آنها به همدیگر است که مهم است. ممکن است به این منظور از Redux ،Context ،useReducer ،useState یا چیزی کاملاً متفاوت استفاده کنیم، اما نکته ماجرا این است که همه این قطعهها زمانی معنیدار میشوند که کنار هم قرار بگیرند. به همین دلیل بر این باوریم که Typescript در این زمینه میتواند نقشی تعیینکننده داشته باشد. اینک به بررسی مزایا و معایب نسخه 3.4 آن میپردازیم.
Typescript چیزی بیش از نوع است
افراد مختلف عموماً عمیقاً بر این باور هستند که انواع داده در جاوا اسکریپت باید الزام شوند. این حرف باعث میشود به این جمله فکر کنیم که:
ما چنان کد مینویسیم که گویی نویسندهای یک متن ادبی مینویسد.
بحثهای بر سر جاوا اسکریپت و تایپاسکریپت عموماً در مورد فرم و ساختار هستند و افراد به طور معمول ریتم را فراموش میکنند. اما کسانی که سابقه کدنویسی بالایی در تایپاسکریپت دارند، عموماً توجه بیشتری به ریتم دارند و بیشتر ترجیح میدهند روی مزیتهایی که تایپاسکریپت عرضه میکند متمرکز شوند.
به بیان دیگر به جای آغاز نوشتن اینترفیسهایی برای props، حالتها و بازگشتی توابع و پیچیده ساختن قابلیتها بدون نوشتن یک خط کد زمان اجرا، بهتر است وارد فرایند کدنویسی شده و از تایپاسکریپت در مسیر خود بهره بگیریم تا همه چیز را به هم متصل کرده و مطمئن شویم که تغییراتی که روی اپلیکیشن اجرا میکنیم یکپارچگی آن را به خطر نمیاندازند. نتیجهگیری این است که نباید کل کانون توجه خود را در زمان توسعه اپلیکیشن روی نوع داده بگذاریم.
بهبود Reducer-ها
پیش از آن که کار خود را با مثالهای کد آغاز کنیم، باید بگوییم که منظور از «بهبود» در عنوان فوق چیست. یک تفسیر ابتدایی از آن چه میخواهیم به دست آوریم به صورت زیر است:
- کدی که کمتر مستعد بروز خطا باشد: زمانی که تعداد کامپوننتها بالا میرود، دوست داریم روی موارد ضروری متمرکز شویم و لازم نباشد که به صورت دستی بررسی کنیم آیا تغییراتی که ایجاد کردهایم با موارد قبلی مطابقت دارد یا نه. تایپاسکریپت این کار را برای ما به صورت خودکار انجام میدهد و بازخورد آنی ارائه میکند.
- عدم نوشتن کد اضافی برای تعاریف نوع: تصور کنید کدبیسی که روی آن کار میکنید، برخی اینترفیسهای ژنریک دارد که به شما کمک میکند نوعهای جدیدی برای موقعیتهای خاص بسازید، اما این وضعیت بهرهوری چندان بالایی ندارد، چون باید توابع کمکی برای همه موقعیتها بنویسید.
- عدم افزونگی کد/نوع (DRY): بدین ترتیب ما روی این نکته تمرکز میکنیم که کد ما نوع را تعیین کند و نه هیچ روش دیگر.
اکنون میخواهیم reducer خود را بسازیم که حالت یک user را کپسولهسازی میکند. فرض کنید میخواهیم حالتی مانند زیر داشته باشیم:
1const initialState = {
2 name: '',
3 points: 0,
4 likesGames: true
5}
6type State = typeof initialState;
با نوشتن type State = typeof initialState هم اینک 50 درصد از تعریف نوعی که میخواهیم داشته باشیم را به دست آوردهایم. ضمناً لحظهای که هرگونه تغییری در این ساختار ایجاد میکنیم، این تغییر روی همه مکانهای لازم انتشار مییابد و مواردی که لازم است بهروزرسانی شوند، هایلایت میشوند. تایپاسکریپت، State را به صورت زیر خواهد خواند:
1type State = {
2 name: string;
3 points: number;
4 likesGames: boolean;
5}
ما برای اکشنهای خود از این الگو استفاده میکنیم و ایجادکنندههای اکشن را از قبل تعریف میکنیم. این الگو هم اینک یکی از رایجترین الگوی های موجود است. همچنین در پروژههایی که به سمت تایپاسکریپت حرکت میکنند این کار میتواند کاربرپسندی بیشتری داشته باشد. یک اکشن میتواند هر تعداد از فیلدهای حالت را به صورت همزمان داشته باشد. برای سادگی یک ایجادکننده اکشن برای هر یک از فیلدهای موجود مینویسیم:
1export function updateName(name: string) {
2 return {
3 type: ‘UPDATE_NAME’,
4 name
5 }
6}
7type Action = ReturnType<typeof updateName>
دستور ReturnType نوع آن چه را که از تابع بازگشت مییابد تعیین میکند. این امکان از نسخه 2.8 تایپاسکریپت عرضه شده است. با استفاده از این امکان دیگر لازم نیست شیء یکسانی را دو بار بنویسیم. اما نسخه 3.4 امکان بسیار بهتری را اضافه کرده است. مشکل کد فوق این است که تا این زمان Action باید به صورت { type: string, name: string } تفسیر میشد و از این رو در زمان اجرای کارهایی مانند زیر هیچ اطلاعات مفیدی در مورد خود اکشن به دست نمیآمد:
1witch(action.type)
1dispatch({ type: 'anything' })
assertion برای Const
اینک میتوانیم نوع هر شیئی را که میخواهیم قفل کنیم و در مورد تغییرناپذیری هم سرنخی به کامپایلر بدهیم. بدین ترتیب:
- هیچ نوع لفظی (literal) در آن عبارت نباید گسترش یافته باشد (برای نمونه نمیتوان از «hello» به یک رشته رسید.)
- لفظهای شیء دارای مشخصههای صرفاً-خواندنی خواهند بود.
- لفظهای آرایه داری چندتاییهای صرفاً-خواندنی خواهند بود.
اکشن بهروزرسانی شده با استفاده از assertion برای const به صورت زیر است:
1export function updateName(name: string) {
2 return <const>{
3 type: ‘UPDATE_NAME’,
4 name
5 }
6}
در نهایت همه نوعبندیها برای اکشنها صرفاً مانند زیر هستند. بدین ترتیب اینک کار ما برای ساخت ابزاری برای رسیدن به بینش بهتر در مورد کد تکمیل شده است.
1type Action = ReturnType<
2 typeof updateName | typeof addPoints | typeof setLikesGames
3>
سخن پایانی
Assertion ها برای Const هنگامی که Reducer-هایی برای ریداکس مینویسیم میتوانند بسیار مفیدتر باشند، چون ایجادکنندههای اکشن الگوهای بسیار رایجتر برای ارسال اکشن محسوب میشوند. البته این صرفاً یک مثال کوچک از ظرفیتهای آن محسوب میشود. اگر از تایپاسکریپت با رویکرد مشابه ما و برای ایجاد نوع به عنوان نتیجهای از پیادهسازی استفاده میکنید، Reducer-ها صرفاً یک شروع محسوب میشوند.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- مجموعه آموزشهای جاوا اسکریپت
- راهنمای جامع React (بخش اول) — از صفر تا صد
- انواع پیشرفته در TypeScript — با مثال های کاربردی
- آموزش React.js در کمتر از ۵ دقیقه — از صفر تا صد
==