در مورد تابع SelectByOrderNumberAsync ، خیر همیشه null نیست. اگر همیشه null بود، امکان تشخیص و صدور خطای تکراری بودن به کاربر وجود نداشت. در واقع درسته که منطق سیستم گفته شماره سفارش باید یکتا باشد، ولی به کاربر نمیشه اعتماد کرد و باید یک عملیات چک کردن وجود داشته باشه که در صورت اشتباه کاربر، به اون اعلام کنه که تکراری هست.
فیلد Message باید اضافه بشه ممنون بابت گزارش.
و در مورد پیشنهادی که دادید، پیشنهاد خوب و صحیحی هست. سعی میکنم در همین آپدیت جدید پیاده سازیش کنم.
تشکر.
اشتراکها
strict mode در جاوا اسکریپت
نظرات مطالب
نوعهای نال نپذیر در TypeScript
برای مشاهده طبقه بندی Bad code smellها میتوانید به اینجا مراجعه کنید.
زمانیکه به ازای هر تغییر، نیاز باشد تغییرات کوچکی در تعداد کلاسهای زیادی انجام شود، این بوی بد کد بوجود آمده است. این الگو از دسته بندی «جلوگیری کنندگان از تغییر» است. نام این دسته بندی به طور واضح گویای مشکلی است که این الگوی بد ایجاد میکند.
چرا چنین بویی به راه میافتد؟
یکی از نشانههای وجود چنین الگوی بدی در کدها، مشاهده کدهای تکراریست. ریشه اصلی این بوی بد، پراکنده کردن مسئولیتها در کلاسهای مختلف است. مسئولیتهایی که بهتر بود در یک کلاس جمع شوند. معمولا برای رفع این بوی بد اقدام به جمع کردن مسئولیتها از نقاط مختلف به یک کلاس میکنند.
با توجه به توضیحات ارائه شده، این بوی بد عملا یکی از علایم اجرایی نکردن اصل Single responsibility و Open closed از اصول طراحی شیء گرایی است. موارد دیگری که در ایجاد چنین مشکلی کمک میکنند به صورت زیر هستند:
- استفاده نادرست از الگوهای طراحی شیء گرا
- عدم درک درست مسئولیتهای کلاسهای ایجاد شده
- عدم تشخیص مکانیزمهای مشترک در کد و جداسازی مناسب آنها
برای بررسی بیشتر این موضوع فرض کنید کلاسهایی در نرم افزار خود دارید که شماره تلفن کاربر را به صورت ورودی دریافت و روی آن کار خاصی را انجام میدهند. در ابتدای تولید نرم افزار فرمت صحیح شماره تلفن به صورت "04135419999" تشخیص داده شده است و مکانیزم اعتبارسنجی آن نیز با استفاده regular expressionها پیاده سازی شدهاست. بعدا نیازمندی دیگری بوجود میآید که شماره تلفنهایی با کد بین المللی نیز در نرم افزار قابل استفاده باشند. مانند "984135410000+" دو نوع پیاده سازی (از میان روشهای فراوان پیاده سازی) برای تشریح این موضوع میتوان متصور بود. فرض کنید در دو موجودیت «کاربر» و «آدرس» نیاز به ذخیره سازی شماره تلفن وجود دارد.
اول: هر جائیکه نیاز به اعتبارسنجی شماره تلفن وجود داشته باشد؛ این کار تماما در همان مکان انجام شود.
public class UserService { public void SaveUser(dynamic userEntity) { var regEx = "blablabla"; var phoneIsValid = Regex.IsMatch(userEntity.PhoneNumber, regEx); if (!phoneIsValid) return; // ... } } public class AddressService { public void SaveAddress(dynamic addressEntity) { var regEx = "blablabla"; var phoneIsValid = Regex.IsMatch(addressEntity.PhoneNumber, regEx); if (!phoneIsValid) return; } }
در این روش پیاده سازی اگر دقت کرده باشید روال مربوط به اعتبارسنجی در دو متد «ذخیره کاربر» و «ذخیره آدرس» تکرار شدهاست . این الگوی کد نویسی، علاوه بر این که خود نوعی بوی بد کد محسوب میشود، باعث ایجاد الگوی Shotgun surgery نیز است.
در اینجا اگر قصد اعمال تغییری در منطق مربوط به اعتبارسنجی شماره تلفن وجود داشته باشد، نیاز خواهد بود تمامی مکانهایی که این منطق پیاده سازی شدهاست، بسته به شرایط جدید تغییر کند. یعنی برای تغییر یک منطق اعتبارسنجی نیاز خواهد بود کلاسهای زیادی تغییر کنند.
دوم: راه بهتر در انجام چنین کاری، جداسازی منطق مربوط به اعتبارسنجی شماره تلفن و انتقال آن به کلاسی جداگانهاست؛ به صورت زیر:
public class PhoneValidator { public bool IsValid(string phoneNumber) { var regEx = "blablabla"; var phoneIsValid = Regex.IsMatch(phoneNumber, regEx); if (!phoneIsValid) return false; return true; } } public class UserService { public void SaveUser(dynamic userEntity) { var validator = new PhoneValidator(); var phoneIsValid = validator.IsValid(userEntity.PhoneNumber); if (!phoneIsValid) return; // ... } } public class AddressService { public void SaveAddress(dynamic addressEntity) { var validator = new PhoneValidator(); var phoneIsValid = validator.IsValid(addressEntity.PhoneNumber); if (!phoneIsValid) return; // ... } }
اگر به تکه کد بالا دقت کنید، مشاهده خواهید کرد که برای اعمال تغییر در منطق اعتبارسنجی شماره تلفن دیگر نیازی نیست به کلاسهای استفاده کننده از آن مراجعه کرد و اعمال تغییر در یک نقطه کد، بر تمامی استفاده کنندگان اثر خواهد گذاشت. یکی دیگر از مزیتهای استفاده از چنین روش پیاده سازی ای، امکان تست نویسی بهتر برای واحدهای مختلف کد است.
شکل دیگر
شکل دیگر این بوی بد کد، Divergent Change است. با این تفاوت که در الگوی Divergent Change تغییرات در یک کلاس اتفاق میافتند نه در چندین کلاس به طور همزمان.
جمع بندی
تشخیص چنین الگوی بد کد نویسی ای همیشه به این سادگی نیست. یکی از راههای تشخیص سریع چنین بوی بد کدی این است که به کارهای تکراری عادت نکنید! و زمانیکه متوجه شدید کار خاصی را در کد به صورت تکراری انجام میدهید، دقت لازم را برای تغییر آن داشته باشید؛ به صورتیکه نیاز به اعمال تغییرات تکراری در مکانهای مختلف کد وجود نداشته باشد. راه دیگر زمانی است که کدی تکراری را مشاهده کردید. زمانیکه کدی تکراری در کدها وجود داشته باشد، اطمینان داشته باشید هنگام تغییر آن به این مشکل دچار خواهید شد. برای رفع موضوع کد تکراری میتوانید از روشهای مختلفی که عنوان شد استفاده کنید.
وقتی تغییری را در اشیاء خود به وجود میآورید، Angular بلافاصله متوجه آنها شده و viewها را به روز رسانی میکند. هدف از این مکانیزم، اطمینان حاصل کردن از همگام بودن اشیاء مدلها و viewها هستند. آگاهی از نحوهی انجام این عملیات، کمک شایانی را به بالابردن کارآیی یک برنامهی با رابط کاربری پیچیدهای میکند. یک شیء مدل در Angular عموما به سه طریق تغییر میکند:
- بروز رخدادهای DOM مانند کلیک
- صدور درخواستهای Ajax ایی
- استفاده از تایمرها (setTimer, setInterval)
ردیابهای تغییرات در Angular
تمام برنامههای Angular در حقیقت سلسله مراتبی از کامپوننتها هستند. در زمان اجرای برنامه، Angular به ازای هر کامپوننت، یک تشخیص دهندهی تغییرات را ایجاد میکند که در نهایت سلسله مراتب ردیابها را همانند سلسله مراتب کامپوننتها ایجاد خواهد کرد. هر زمانیکه ردیابی فعال میشود، Angular این درخت را پیموده و مواردی را که تغییراتی را گزارش دادهاند، بررسی میکند. این پیمایش به ازای هر تغییر رخ دادهی در مدلهای برنامه صورت میگیرد و همواره از بالای درخت شروع شده و به صورت ترتیبی تا پایین آن ادامه پیدا میکند:
این پیمایش ترتیبی از بالا به پایین، از این جهت صورت میگیرد که اطلاعات کامپوننتها از والدین آنها تامین میشوند. تشخیص دهندههای تغییرات، روشی را جهت ردیابی وضعیت پیشین و فعلی یک کامپوننت ارائه میدهد تا Angular بتواند تغییرات رخداده را منعکس کند. اگر Angular گزارش تغییری را از یک تشخیص دهندهی تغییر دریافت کند، کامپوننت مرتبط را مجددا ترسیم کرده و DOM را به روز رسانی میکند.
استراتژیهای تشخیص تغییرات در Angular
برای درک نحوهی عملکرد سیستم تشخیص تغییرات نیاز است با مفهوم value types و reference types در JavaScript آشنا شویم. در JavaScript نوعهای زیر value type هستند:
و نوعهای زیر Reference type محسوب میشوند:
مهمترین تفاوت بین این دو نوع، این است که برای دریافت مقدار یک value type فقط کافی است از stack memory کوئری بگیریم. اما برای دریافت مقادیر reference types باید ابتدا در جهت یافتن شماره ارجاع آن، از stack memory کوئری گرفته و سپس بر اساس این شماره ارجاع، اصل مقدار آنرا در heap memory پیدا کنیم.
این دو تفاوت را میتوان در شکل زیر بهتر مشاهده کرد:
استراتژی Default یا پیشفرض تشخیص تغییرات در Angular
همانطور که پیشتر نیز عنوان شد، Angular تغییرات یک شیء مدل را در جهت تشخیص تغییرات و انعکاس آنها به View برنامه، ردیابی میکند. در این حالت هر تغییری بین حالت فعلی و پیشین یک شیء مدل برای این منظور بررسی میگردد. در اینجا Angular این سؤال را مطرح میکند: آیا مقداری در این مدل تغییر یافتهاست؟
اما برای یک reference type میتوان سؤالات بهتری را مطرح کرد که بهینهتر و سریعتر باشند. اینجاست که استراتژی OnPush تشخیص تغییرات مطرح میشود.
استراتژی OnPush تشخیص تغییرات در Angular
ایده اصلی استراتژی OnPush تشخیص تغییرات در Angular در immutable فرض کردن reference types نهفتهاست. در این حالت هر تغییری در شیء مدل، سبب ایجاد یک ارجاع جدید به آن در stack memory میشود. به این ترتیب میتوان تشخیص تغییرات بسیار سریعتری را شاهد بود. چون دیگر در اینجا نیازی نیست تمام مقادیر یک شیء را مدام تحت نظر قرار داد. همینقدر که ارجاع آن در stack memory تغییر کند، یعنی مقادیر این شیء در heap memory تغییر یافتهاند.
در این حالت Angular دو سؤال را مطرح میکند: آیا ارجاع به یک reference type در stack memory تغییر یافتهاست؟ اگر بله، آیا مقادیر آن در heap memory تغییر کردهاند؟ برای مثال جهت بررسی تغییرات یک آرایهی با 30 عضو، دیگر در ابتدای کار نیازی نیست تا هر 30 عضو آن بررسی شوند (برخلاف حالت پیشفرض بررسی تغییرات). در حالت استراتژی OnPush، ابتدا مقدار ارجاع این آرایه در stack memory بررسی میشود. اگر تغییری در آن صورت گرفته بود، به معنای تغییری در اعضای آرایهاست.
استراتژی OnPush در یک کامپوننت به نحو ذیل فعال و انتخاب میشود و مقدار پیشفرض آن ChangeDetectionStrategy.Default است:
استراتژی OnPush تغییرات یک کامپوننت را در حالتهای ذیل نیز ردیابی میکند:
- اگر مقدار یک خاصیت از نوع Input@ تغییر کند.
- اگر یک event handler رخدادی را صادر کند.
- اگر سیستم ردیابی به صورت دستی فراخوانی شود.
- اگر سیستم ردیاب تغییرات child component آن، اجرا شود.
نوعهای ارجاعی Immutable
همانطور که عنوان شد، شرط کار کردن استراتژی OnPush، داشتن نوعهای ارجاعی immutable است. اما نوع ارجاعی immutable چیست؟
Immutable بودن به زبان ساده به این معنا است که ما هیچگاه جهت تغییر مقدار خاصیتی در یک شیء، آن خاصیت را مستقیما مقدار دهی نمیکنیم؛ بلکه کل شیء را مجددا مقدار دهی میکنیم.
برای نمونه در مثال زیر، خاصیت foo شیء before مستقیما مقدار دهی شدهاست:
اما اگر بخواهیم با آن به صورت Immutable «رفتار» کنیم، کل این شیء را جهت اعمال تغییرات، مقدار دهی مجدد خواهیم کرد:
البته باید دقت داشت در هر دو مثال، شیءهای ایجاد شده در اصل mutable هستند؛ اما در مثال دوم، با این شیء به صورت immutable «رفتار» شدهاست و صرفا «تظاهر» به رفتار immutable با یک شیء ارجاعی صورت گرفتهاست.
معرفی کتابخانهی Immutable.js
جهت ایجاد اشیاء واقعی immutable کتابخانهی Immutable.js توسط Facebook ایجاد شدهاست و برای کار با استراتژی تشخیص تغییرات OnPush در Angular بسیار مناسب است.
برای نصب آن دستور ذیل را صادر نمائید:
یک نمونه مثال از کاربرد ساختارهای دادهی List و Map آن برای کار با آرایهها و اشیاء:
تغییر ارجاع به یک شیء با استفاده از spread properties
در این مثال تنها خاصیت age شیء user به روز رسانی میشود. بنابراین ارجاع به این شیء تغییر نخواهد کرد و اگر از روش changeDetection: ChangeDetectionStrategy.OnPush استفاده کنیم، رابط کاربری برنامه به روز رسانی نخواهد شد و این تغییر صرفا با بررسی عمیق تک تک خواص این شیء با وضعیت قبلی آن قابل تشخیص است (یا همان حالت پیش فرض بررسی تغییرات در Angular و نه حالت OnPush).
اگر علاقمند به استفادهی از یک کتابخانهی اضافی مانند Immutable.js در کدهای خود نباشید، روش دیگری نیز برای تغییر ارجاع به یک شیء وجود دارد:
در اینجا با استفاده از spread properties یک شیء کاملا جدید ایجاد شدهاست و ارجاع به آن با ارجاع به شیء user یکی نیست.
نمونهی دیگر آن در حین کار با متد push یک آرایهاست:
متد push، بدون تغییر ارجاعی به آرایهی اصلی، عضوی را به آن آرایه اضافه میکند. بنابراین اعضای اضافه شدهی به آن نیز توسط استراتژی OnPush قابل تشخیص نیستند. اما اگر بجای متد push از spread operator استفاده کنیم:
اینبار this.food به یک شیء کاملا جدید اشاره میکند که ارجاع به آن، با ارجاع به شیء this.food قبلی یکی نیست. بنابراین استراتژی OnPush قابلیت تشخیص تغییرات آنرا دارد.
آگاه سازی دستی موتور تشخیص تغییرات Angular در حالت استفادهی از استراتژی OnPush
تا اینجا دریافتیم که استراتژی OnPush تنها به تغییرات ارجاعات به اشیاء پاسخ میدهد و به نحوی باید این ارجاع را با هر به روز رسانی تغییر داد. اما روش دیگری نیز برای وادار کردن این سیستم به تغییر وجود دارد:
در این کامپوننت از استراتژی OnPush استفاده شدهاست. در اینجا میتوان همانند قبل با اشیاء و آرایههای موجود کار کرد (بدون اینکه ارجاعات به آنها را تغییر دهیم و یا آنها را immutable کنیم) و در پایان کار، متد detectChanges سرویس ChangeDetectorRef را به صورت دستی فراخوانی کرد تا Angular کار رندر مجدد view این کامپوننت را بر اساس تغییرات آن انجام دهد (کل کامپوننت به عنوان یک کامپوننت تغییر کرده به سیستم ردیابی تغییرات معرفی میشود).
کار با Observables در حالت استفادهی از استراتژی OnPush
مطلب «صدور رخدادها از سرویسها به کامپوننتها در برنامههای Angular» را در نظر بگیرید. Observables نیز ما را از تغییرات رخداده آگاه میکنند؛ اما برخلاف immutable objects با هر تغییری که رخ میدهد، ارجاع به آنها تغییری نمیکند. آنها تنها رخدادی را به مشترکین، جهت اطلاع رسانی از تغییرات صادر میکنند.
بنابراین اگر از Observables و استراتژی OnPush استفاده کنیم، چون ارجاع به آنها تغییری نمیکند، رخدادهای صادر شدهی توسط آنها ردیابی نخواهند شد. برای رفع این مشکل، امکان علامتگذاری رخدادهای Observables به تغییر کرده پیشبینی شدهاست.
در اینجا کامپوننتی را داریم که قابلیت صدور رخدادها را از طریق یک BehaviorSubject دارد:
و کامپوننت دیگری توسط خاصیت ورودی data از نوع Observable در متد ngOnInit، مشترک آن خواهد شد:
در این حالت چون از ChangeDetectionStrategy.OnPush استفاده میشود و ارجاع به this.data این observable با هر بار صدور رخدادی توسط آن، تغییر نمیکند، سیستم ردیابی تغییرات آنرا به عنوان تغییر کرده درنظر نمیگیرد. برای رفع این مشکل تنها کافی است رخدادگردان آنرا با متد markForCheck علامتگذاری کنیم:
markForCheck به Angular اعلام میکند که این مسیر ویژه را در بررسی بعدی سیستم ردیابی تغییرات لحاظ کن.
- بروز رخدادهای DOM مانند کلیک
- صدور درخواستهای Ajax ایی
- استفاده از تایمرها (setTimer, setInterval)
ردیابهای تغییرات در Angular
تمام برنامههای Angular در حقیقت سلسله مراتبی از کامپوننتها هستند. در زمان اجرای برنامه، Angular به ازای هر کامپوننت، یک تشخیص دهندهی تغییرات را ایجاد میکند که در نهایت سلسله مراتب ردیابها را همانند سلسله مراتب کامپوننتها ایجاد خواهد کرد. هر زمانیکه ردیابی فعال میشود، Angular این درخت را پیموده و مواردی را که تغییراتی را گزارش دادهاند، بررسی میکند. این پیمایش به ازای هر تغییر رخ دادهی در مدلهای برنامه صورت میگیرد و همواره از بالای درخت شروع شده و به صورت ترتیبی تا پایین آن ادامه پیدا میکند:
این پیمایش ترتیبی از بالا به پایین، از این جهت صورت میگیرد که اطلاعات کامپوننتها از والدین آنها تامین میشوند. تشخیص دهندههای تغییرات، روشی را جهت ردیابی وضعیت پیشین و فعلی یک کامپوننت ارائه میدهد تا Angular بتواند تغییرات رخداده را منعکس کند. اگر Angular گزارش تغییری را از یک تشخیص دهندهی تغییر دریافت کند، کامپوننت مرتبط را مجددا ترسیم کرده و DOM را به روز رسانی میکند.
استراتژیهای تشخیص تغییرات در Angular
برای درک نحوهی عملکرد سیستم تشخیص تغییرات نیاز است با مفهوم value types و reference types در JavaScript آشنا شویم. در JavaScript نوعهای زیر value type هستند:
• Boolean • Null • Undefined • Number • String
• Arrays • Objects • Functions
این دو تفاوت را میتوان در شکل زیر بهتر مشاهده کرد:
استراتژی Default یا پیشفرض تشخیص تغییرات در Angular
همانطور که پیشتر نیز عنوان شد، Angular تغییرات یک شیء مدل را در جهت تشخیص تغییرات و انعکاس آنها به View برنامه، ردیابی میکند. در این حالت هر تغییری بین حالت فعلی و پیشین یک شیء مدل برای این منظور بررسی میگردد. در اینجا Angular این سؤال را مطرح میکند: آیا مقداری در این مدل تغییر یافتهاست؟
اما برای یک reference type میتوان سؤالات بهتری را مطرح کرد که بهینهتر و سریعتر باشند. اینجاست که استراتژی OnPush تشخیص تغییرات مطرح میشود.
استراتژی OnPush تشخیص تغییرات در Angular
ایده اصلی استراتژی OnPush تشخیص تغییرات در Angular در immutable فرض کردن reference types نهفتهاست. در این حالت هر تغییری در شیء مدل، سبب ایجاد یک ارجاع جدید به آن در stack memory میشود. به این ترتیب میتوان تشخیص تغییرات بسیار سریعتری را شاهد بود. چون دیگر در اینجا نیازی نیست تمام مقادیر یک شیء را مدام تحت نظر قرار داد. همینقدر که ارجاع آن در stack memory تغییر کند، یعنی مقادیر این شیء در heap memory تغییر یافتهاند.
در این حالت Angular دو سؤال را مطرح میکند: آیا ارجاع به یک reference type در stack memory تغییر یافتهاست؟ اگر بله، آیا مقادیر آن در heap memory تغییر کردهاند؟ برای مثال جهت بررسی تغییرات یک آرایهی با 30 عضو، دیگر در ابتدای کار نیازی نیست تا هر 30 عضو آن بررسی شوند (برخلاف حالت پیشفرض بررسی تغییرات). در حالت استراتژی OnPush، ابتدا مقدار ارجاع این آرایه در stack memory بررسی میشود. اگر تغییری در آن صورت گرفته بود، به معنای تغییری در اعضای آرایهاست.
استراتژی OnPush در یک کامپوننت به نحو ذیل فعال و انتخاب میشود و مقدار پیشفرض آن ChangeDetectionStrategy.Default است:
import {ChangeDetectionStrategy, Component} from '@angular/core'; @Component({ // ... changeDetection: ChangeDetectionStrategy.OnPush }) export class OnPushComponent { // ... }
- اگر مقدار یک خاصیت از نوع Input@ تغییر کند.
- اگر یک event handler رخدادی را صادر کند.
- اگر سیستم ردیابی به صورت دستی فراخوانی شود.
- اگر سیستم ردیاب تغییرات child component آن، اجرا شود.
نوعهای ارجاعی Immutable
همانطور که عنوان شد، شرط کار کردن استراتژی OnPush، داشتن نوعهای ارجاعی immutable است. اما نوع ارجاعی immutable چیست؟
Immutable بودن به زبان ساده به این معنا است که ما هیچگاه جهت تغییر مقدار خاصیتی در یک شیء، آن خاصیت را مستقیما مقدار دهی نمیکنیم؛ بلکه کل شیء را مجددا مقدار دهی میکنیم.
برای نمونه در مثال زیر، خاصیت foo شیء before مستقیما مقدار دهی شدهاست:
static mutable() { var before = {foo: "bar"}; var current = before; current.foo = "hello"; console.log(before === current); // => true }
static immutable() { var before = {foo: "bar"}; var current = before; current = {foo: "hello"}; console.log(before === current); // => false }
معرفی کتابخانهی Immutable.js
جهت ایجاد اشیاء واقعی immutable کتابخانهی Immutable.js توسط Facebook ایجاد شدهاست و برای کار با استراتژی تشخیص تغییرات OnPush در Angular بسیار مناسب است.
برای نصب آن دستور ذیل را صادر نمائید:
npm install immutable --save
import {Map, List} from 'immutable'; var foobar = {foo: "bar"}; var immutableFoobar = Map(foobar); console.log(immutableFooter.get("foo")); // => bar var helloWorld = ["Hello", "World!"]; var immutableHelloWorld = List(helloWorld); console.log(immutableHelloWorld.first()); // => Hello console.log(immutableHelloWorld.last()); // => World! helloWorld.push("Hello Mars!"); console.log(immutableHelloWorld.last()); // => Hello Mars!
تغییر ارجاع به یک شیء با استفاده از spread properties
const user = { name: 'Max', age: 30 } user.age = 31
اگر علاقمند به استفادهی از یک کتابخانهی اضافی مانند Immutable.js در کدهای خود نباشید، روش دیگری نیز برای تغییر ارجاع به یک شیء وجود دارد:
const user = { name: 'Max', age: 30 } const modifiedUser = { ...user, age: 31 }
نمونهی دیگر آن در حین کار با متد push یک آرایهاست:
export class AppComponent { foods = ['Bacon', 'Lettuce', 'Tomatoes']; addFood(food) { this.foods.push(food); } }
addFood(food) { this.foods = [...this.foods, food]; }
آگاه سازی دستی موتور تشخیص تغییرات Angular در حالت استفادهی از استراتژی OnPush
تا اینجا دریافتیم که استراتژی OnPush تنها به تغییرات ارجاعات به اشیاء پاسخ میدهد و به نحوی باید این ارجاع را با هر به روز رسانی تغییر داد. اما روش دیگری نیز برای وادار کردن این سیستم به تغییر وجود دارد:
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; @Component({ selector: 'app-child', templateUrl: './child.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class ChildComponent { @Input() data: string[]; constructor(private cd: ChangeDetectorRef) {} refresh() { this.cd.detectChanges(); } }
کار با Observables در حالت استفادهی از استراتژی OnPush
مطلب «صدور رخدادها از سرویسها به کامپوننتها در برنامههای Angular» را در نظر بگیرید. Observables نیز ما را از تغییرات رخداده آگاه میکنند؛ اما برخلاف immutable objects با هر تغییری که رخ میدهد، ارجاع به آنها تغییری نمیکند. آنها تنها رخدادی را به مشترکین، جهت اطلاع رسانی از تغییرات صادر میکنند.
بنابراین اگر از Observables و استراتژی OnPush استفاده کنیم، چون ارجاع به آنها تغییری نمیکند، رخدادهای صادر شدهی توسط آنها ردیابی نخواهند شد. برای رفع این مشکل، امکان علامتگذاری رخدادهای Observables به تغییر کرده پیشبینی شدهاست.
در اینجا کامپوننتی را داریم که قابلیت صدور رخدادها را از طریق یک BehaviorSubject دارد:
import { Component } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Component({ ... }) export class AppComponent { foods = new BehaviorSubject(['Bacon', 'Letuce', 'Tomatoes']); addFood(food) { this.foods.next(food); } }
import { Component, Input, ChangeDetectionStrategy, ChangeDetectorRef, OnInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; @Component({ selector: 'app-child', templateUrl: './child.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class ChildComponent implements OnInit { @Input() data: Observable<any>; foods: string[] = []; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.data.subscribe(food => { this.foods = [...this.foods, ...food]; }); }
ngOnInit() { this.data.subscribe(food => { this.foods = [...this.foods, ...food]; this.cd.markForCheck(); // marks path }); }
نظرات مطالب
چند نکته کاربردی درباره Entity Framework
قسمت Find متد Update شما در حالت detached اضافی است. یعنی اگر میدونید که این Id در دیتابیس وجود داره نیازی به Findاش نیست. فقط State اون رو تغییر بدید کار میکنه.آیا این قسمت از کد برای تشخیص objectهای تکراری در گراف objectهای ما نیست؟ ونبایستی uncomment شود؟ طبق این لینک .
روشی را که تا اینجا در مورد MobX بررسی کردیم، تا نگارش 5x آنرا پوشش میدهد. در همین زمان، کتابخانهی دیگری به نام mobx-react-lite ارائه شد که به همراه تعدادی Hook مخصوص MobX بود تا با سیستم جدید React که مبتنی بر Hooks است، سازگار شود. این امکانات در حال حاضر با خود کتابخانهی mobx-react 6x یکپارچه شده و به زودی mobx-react-lite منسوخ شده اعلام میشود. البته روش inject/observer بررسی شدهی تا نگارش 5x آن، هنوز هم برقرار است و قرار نیست که به این زودیها منسوخ شده اعلام شود. به همین جهت نکاتی را که در مطلب جاری بررسی میکنیم، به عنوان روش تکمیلی سازگار با نگارش جاری 6x آن مطرح است و در کل با هر روشی که علاقمند بودید میتوانید با MobX کار کنید. البته باز هم توصیه شدهاست که سیستم Provider آنرا با React Context استاندارد، جایگزین کنید؛ چون احتمال حذف آن در نگارشهای بعدی MobX هست.
به صورت خلاصه:
- اگر فقط از کامپوننتهای کلاسی استفاده میکنید، mobx-react@5 برای کار شما پاسخگو است.
- اگر از کامپوننتهای کلاسی و همچنین کامپوننتهای تابعی در برنامهی خود استفاده میکنید، mobx-react@6 به همراه mobx-react-lite نیز ارائه میشود و هر دو روش را با هم پوشش میدهد.
- اگر فقط از کامپوننتهای تابعی جدید استفاده میکنید، هوکهای کتابخانهی کوچک mobx-react-lite برای کار شما کافی است.
معرفی useLocalStore Hook و useObserver Hook
در مطالب قبلی، روش تعریف یک کلاس مخزن حالت MobX را توسط تزئین کنندههایی مانند observable، computed و action بررسی کردیم. همچنین دریافتیم که تعریف یک چنین تزئین کنندههایی، یا نیاز به استفادهی از تایپاسکریپت را دارد و یا باید پروژهی React را جهت تغییر کامپایلر Babel آن و فعالسازی decorators، مقداری ویرایش کرد. با useLocalStore Hook هرچند تمام روشهای قبلی هنوز هم پشتیبانی میشوند، اما دیگر نیاز به استفادهی از decorators نیست. useLocalStore تابعی است که یک شیء را باز میگرداند. هر خاصیتی از این شیء، به صورت خودکار observable درنظر گرفته میشود. تمام getters آن به عنوان computed properties تفسیر میشوند و تمام متدهای آن، action درنظر گرفته خواهند شد.
یک مثال:
- در اینجا نحوهی import تابع useLocalStore را از کتابخانهی mobx-react نگارش 6x ملاحظه میکنید.
- روش استفادهی از تابع useLocalStore، میتواند به صورت محلی (همانند اسم آن) مختص به یک کامپوننت باشد. یعنی میتوان بجای state استاندارد React که اجازهی تغییر مستقیم خواص آنرا نمیدهد، از MobX State محلی ارائه شدهی توسط useLocalStore استفاده کرد و یا میتوان useLocalStore را به صورت global نیز تعریف کرد که در ادامهی بحث به آن میپردازیم.
- در مثال فوق، طول عمر شیء ایجاد شدهی توسط useLocalStore، محلی و محدود به طول عمر کامپوننت تابعی تعریف شدهاست.
- در اینجا شیء بازگشت داده شدهی توسط useLocalStore، دارای دو خاصیت title و done است. این دو خاصیت بدون نیاز به هیچ تعریف خاصی، observable در نظر گرفته میشوند. Fi به علاوه خاصیت getter آن به نام emoji نیز به عنوان یک خاصیت محاسباتی MobX تفسیر شده و متد toggle آن به صورت یک action پردازش میشود. بنابراین در حین کار با MobX Hooks دیگر نیازی به تغییر ساختار پروژهی React، برای پشتیبانی از decorators نیست.
- در این مثال، return useObserver را نیز مشاهده میکنید. کار آن رندر مجدد کامپوننت، با تغییر یکی از خواص observable ردیابی شدهی توسط آن است.
امکان تعریف global state با کمک useLocalStore
نام useLocalStore از این جهت انتخاب شدهاست که مشخص کند مخزن حالت ایجاد شدهی توسط آن، درون یک کامپوننت به صورت محلی ایجاد میشود و سراسری نیست. اما این نکته به این معنا نیست که نمیتوان مخزن حالت ایجاد شدهی توسط آنرا در بین سلسه مراتب کامپوننتهای برنامه به اشتراک گذاشت. توسط تابع useLocalStore میتوان چندین مخزن حالت را ایجاد کرد و سپس توسط شیءای دیگر آنها را یکی کرده و در آخر به کمک Context API خود React آنرا در اختیار تمام کامپوننتهای برنامه قرار داد.
تا نگارش MobX 5x (و همچنین پس از آن)، توسط inject@ میتوان یک مخزن حالت را در اختیار یک کامپوننت قرار داد (مانند inject('myStore')). طراحی inject@ مربوط است به زمانیکه امکان دسترسی به Context پشت صحنهی React به صورت عمومی توسط Context API آن ارائه نشده بود. به همین جهت از این پس دیگر نیازی به استفادهی از آن نیست.
چگونه توسط MobX Hooks، یک مخزن حالت سراسری را ایجاد کنیم؟
برای ایجاد یک مخزن حالت سراسری با روش جدید MobX Hooks، مراحل زیر را میتوان طی کرد:
الف) ایجاد شیء store
ابتدا متدی را مانند createStore ایجاد میکنیم، به نحوی که یک شیء را بازگشت دهد. این شیء همانطور که عنوان شد، خواصش، getters و متدهای آن، توسط MobX ردیابی خواهند شد (مانند const todo = useLocalStore مثال فوق) و نیازی به اعمال MobX Decorators را ندارند.
ب) برپایی Context
اینبار دیگر نه از شیء Provider خود MobX استفاده میکنیم و نه از تزئین کنندهی inject@ آن؛ بلکه از React Context استاندارد استفاده خواهیم کرد:
- در اینجا فرض شدهاست که تابع createStore که شیء store ما را ارائه میدهد از ماژولی به نام createStore دریافت میشود.
- سپس توسط React.createContext، یک شیء Context استاندارد React را ایجاد میکنیم؛ به نام storeContext.
- تابع کمکی StoreProvider، جایگزین شیء Provider قبلی MobX میشود. یعنی کارش محصور کردن کامپوننت App برنامه است تا شیء store را در اختیار سلسه مراتب کامپوننتهای React قرار دهد. در اینجا children به همان کامپوننتهایی که قرار است توسط Context.Provider محصور شوند اشاره میکند.
- تابع کمکی useStore، جهت محصور کردن متد React.useContext، اضافه شدهاست. میتوانید useContext Hook را به صورت مستقیم در کامپوننتهای تابعی فراخوانی کنید و یا میتوانید از متد کمکی useStore بجای آن استفاده نمائید تا حجم کدهای تکراری برنامه کاهش یابد.
ج) استفادهی از StoreProvider تهیه شده
اکنون با استفاده از متد StoreProvider فوق که شیء Context.Provider استاندارد React را بازگشت میدهد، میتوان کامپوننتهای بالاترین کامپوننت سلسه مراتب کامپوننتهای برنامه را محصور کرد، تا تمام آنها بتوانند به store ذخیره شدهی در Provider، دسترسی پیدا کنند:
د) استفاده از store مهیا شده در کامپوننتهای تابعی برنامه
پس از تهیهی متدی کمکی useStore که در حقیقت همان useContext Hook است، میتوان به کمک آن در کامپوننتهای تابعی، به store و تمام امکانات آن دسترسی پیدا کرد:
به این ترتیب دیگر نیازی به inject@ نخواهد بود.
سؤال: آیا هنوز هم میتوان یک مخزن پیچیدهی متشکل از چندین کلاس را تشکیل داد؟
پاسخ: بله. برای مثال ابتدا دو کلاس جدید CounterStore و ThemeStore را به نحو متداولی، با استفادهی از MobX decorators طراحی میکنیم (دقیقا مانند مثال قسمت قبل). سپس بجای ذکر نال، بجای پارامتر متد createContext، آنرا با یک شیء جدید مقدار دهی میکنیم که هر کدام از خواص آن، به یک وهله از مخازن حالت ایجاد شده اشاره میکند:
با این تعییر اگر در کامپوننتی از برنامه نیاز به برای مثال شیء منتسب به خاصیت counterStore را داشتیم، میتوان به صورت زیر عمل کرد:
چند نکتهی تکمیلی
نکته 1: با اشیاء MobX از Object Destructuring استفاده نکنید!
اگر بر روی اشیاء MobX از Object Destructuring استفاده کنیم، خروجی آن تبدیل به متغیرهای سادهای خواهند شد که دیگر ردیابی نمیشوند.
برای مثال اگر counterStore مثال فوق به همراه خاصیت observable ای به نام activeUserName است، آنرا به صورت زیر تبدیل به متغیر activeUserName نکنید؛ چون دیگر reactive نخواهد بود:
فقط بالاترین سطح مخزن را به صورت زیر توسط Object Destructuring از آن استخراج و سپس استفاده کنید:
نکته 2: مدیریت side effects با MobX
در مورد اثرات جانبی و side effects در مطلب «قسمت 32 - React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور» بیشتر بحث شد. اگر یک اثر جانبی مانند تنظیم document.title، به مقدار یک خاصیت observable وابسته بود، میتوان از متد autorun که تغییرات آنها را ردیابی میکند، درون useEffect Hook استاندارد، استفاده کرد:
در حین کار با MobX، هیچگاه نیازی به ذکر وابستگیهای تابع useEffect نیست؛ چون اساسا وجود خارجی ندارند و توسط خود MobX مدیریت میشوند و به store وابستهاند و نه به حالت کامپوننت جاری.
نکته 4: روش فعالسازی MobX strict mode
اگر strict mode را در Mobx به روش زیر فعال کنیم:
پس از آن باید حالت مدیریت شدهی توسط MobX را فقط و فقط توسط actionهای آن تغییر داد و اگر سعی در تغییر مقدار مستقیم یک خاصیت observable کنیم، استثنایی صادر خواهد شد. برای تغییر خواص observable باید آنها را درون یک action قرار داد؛ تا مطابق رهنمودهای طراحی کلاسهای MobX باشد.
نکته 3: روش انجام اعمال async در MobX
فرض کنید یک عملیات async را در یک اکشن متد کلاس حالت MobX، به صورت زیر انجام دادهایم و نتیجهی آن به خاصیت weatherData آن کلاس که observable است، به صورت مستقیم انتساب داده شدهاست:
هرچند loadWeather یک متد را ارائه میدهد که به صورت action معرفی شدهاست، اما هرچیزی که داخل آن قرار میگیرد، الزاما تحت کنترل آن نیست. برای مثال متد then، یک تابع callback جدید را فراخوانی میکند که اعمال آن، تحت کنترل loadWeather نیست. به همین جهت اگر strict mode را فعال کرده باشیم، عنوان میکند که خواص observable را باید درون یک اکشن متد تغییر داد و نه به صورت مستقیم؛ مانند this.weatherData در اینجا.
راه حل اول: تغییر خاصیت this.weatherData را به یک اکشن متد مجزا انتقال میدهیم:
اکنون میتوان قسمت then را به صورت then(data => this.setWeather(data)) نوشت و خطای یاد شده برطرف میشود.
راه حل دوم: اگر نمیخواهیم یک اکشن متد جدید را تعریف کنیم، میتوان از متد کمکی runInAction در داخل یک callback استفاده کرد:
runInAction یکی از متدهای قابل دریافت از mobx است.
در مورد اعمال async/await چطور؟
در اینجا هم تفاوتی نمیکند. هر چیزی پس از await، شبیه به حالت متد then پردازش میشود. به همین جهت در اینجا نیز باید از یکی از دو راه حل ارائه شده، استفاده کرد:
به صورت خلاصه:
- اگر فقط از کامپوننتهای کلاسی استفاده میکنید، mobx-react@5 برای کار شما پاسخگو است.
- اگر از کامپوننتهای کلاسی و همچنین کامپوننتهای تابعی در برنامهی خود استفاده میکنید، mobx-react@6 به همراه mobx-react-lite نیز ارائه میشود و هر دو روش را با هم پوشش میدهد.
- اگر فقط از کامپوننتهای تابعی جدید استفاده میکنید، هوکهای کتابخانهی کوچک mobx-react-lite برای کار شما کافی است.
معرفی useLocalStore Hook و useObserver Hook
در مطالب قبلی، روش تعریف یک کلاس مخزن حالت MobX را توسط تزئین کنندههایی مانند observable، computed و action بررسی کردیم. همچنین دریافتیم که تعریف یک چنین تزئین کنندههایی، یا نیاز به استفادهی از تایپاسکریپت را دارد و یا باید پروژهی React را جهت تغییر کامپایلر Babel آن و فعالسازی decorators، مقداری ویرایش کرد. با useLocalStore Hook هرچند تمام روشهای قبلی هنوز هم پشتیبانی میشوند، اما دیگر نیاز به استفادهی از decorators نیست. useLocalStore تابعی است که یک شیء را باز میگرداند. هر خاصیتی از این شیء، به صورت خودکار observable درنظر گرفته میشود. تمام getters آن به عنوان computed properties تفسیر میشوند و تمام متدهای آن، action درنظر گرفته خواهند شد.
یک مثال:
import React from 'react' import { useLocalStore, useObserver } from 'mobx-react' // 6.x export const SmartTodo = () => { const todo = useLocalStore(() => ({ title: 'Click to toggle', done: false, toggle() { todo.done = !todo.done }, get emoji() { return todo.done ? '😜' : '🏃' }, })) return useObserver(() => ( <h3 onClick={todo.toggle}> {todo.title} {todo.emoji} </h3> )) }
- روش استفادهی از تابع useLocalStore، میتواند به صورت محلی (همانند اسم آن) مختص به یک کامپوننت باشد. یعنی میتوان بجای state استاندارد React که اجازهی تغییر مستقیم خواص آنرا نمیدهد، از MobX State محلی ارائه شدهی توسط useLocalStore استفاده کرد و یا میتوان useLocalStore را به صورت global نیز تعریف کرد که در ادامهی بحث به آن میپردازیم.
- در مثال فوق، طول عمر شیء ایجاد شدهی توسط useLocalStore، محلی و محدود به طول عمر کامپوننت تابعی تعریف شدهاست.
- در اینجا شیء بازگشت داده شدهی توسط useLocalStore، دارای دو خاصیت title و done است. این دو خاصیت بدون نیاز به هیچ تعریف خاصی، observable در نظر گرفته میشوند. Fi به علاوه خاصیت getter آن به نام emoji نیز به عنوان یک خاصیت محاسباتی MobX تفسیر شده و متد toggle آن به صورت یک action پردازش میشود. بنابراین در حین کار با MobX Hooks دیگر نیازی به تغییر ساختار پروژهی React، برای پشتیبانی از decorators نیست.
- در این مثال، return useObserver را نیز مشاهده میکنید. کار آن رندر مجدد کامپوننت، با تغییر یکی از خواص observable ردیابی شدهی توسط آن است.
امکان تعریف global state با کمک useLocalStore
نام useLocalStore از این جهت انتخاب شدهاست که مشخص کند مخزن حالت ایجاد شدهی توسط آن، درون یک کامپوننت به صورت محلی ایجاد میشود و سراسری نیست. اما این نکته به این معنا نیست که نمیتوان مخزن حالت ایجاد شدهی توسط آنرا در بین سلسه مراتب کامپوننتهای برنامه به اشتراک گذاشت. توسط تابع useLocalStore میتوان چندین مخزن حالت را ایجاد کرد و سپس توسط شیءای دیگر آنها را یکی کرده و در آخر به کمک Context API خود React آنرا در اختیار تمام کامپوننتهای برنامه قرار داد.
تا نگارش MobX 5x (و همچنین پس از آن)، توسط inject@ میتوان یک مخزن حالت را در اختیار یک کامپوننت قرار داد (مانند inject('myStore')). طراحی inject@ مربوط است به زمانیکه امکان دسترسی به Context پشت صحنهی React به صورت عمومی توسط Context API آن ارائه نشده بود. به همین جهت از این پس دیگر نیازی به استفادهی از آن نیست.
چگونه توسط MobX Hooks، یک مخزن حالت سراسری را ایجاد کنیم؟
برای ایجاد یک مخزن حالت سراسری با روش جدید MobX Hooks، مراحل زیر را میتوان طی کرد:
الف) ایجاد شیء store
ابتدا متدی را مانند createStore ایجاد میکنیم، به نحوی که یک شیء را بازگشت دهد. این شیء همانطور که عنوان شد، خواصش، getters و متدهای آن، توسط MobX ردیابی خواهند شد (مانند const todo = useLocalStore مثال فوق) و نیازی به اعمال MobX Decorators را ندارند.
export function createStore() { return { // ... } }
ب) برپایی Context
اینبار دیگر نه از شیء Provider خود MobX استفاده میکنیم و نه از تزئین کنندهی inject@ آن؛ بلکه از React Context استاندارد استفاده خواهیم کرد:
import React from 'react'; import { createStore } from './createStore'; import { useLocalStore } from 'mobx-react'; // 6.x or mobx-react-lite@1.4.0 const storeContext = React.createContext(null); export const StoreProvider = ({ children }) => { const store = useLocalStore(createStore); return <storeContext.Provider value={store}>{children}</storeContext.Provider>; } export const useStore = () => { const store = React.useContext(storeContext); if (!store) { throw new Error('useStore must be used within a StoreProvider.'); } return store }
- سپس توسط React.createContext، یک شیء Context استاندارد React را ایجاد میکنیم؛ به نام storeContext.
- تابع کمکی StoreProvider، جایگزین شیء Provider قبلی MobX میشود. یعنی کارش محصور کردن کامپوننت App برنامه است تا شیء store را در اختیار سلسه مراتب کامپوننتهای React قرار دهد. در اینجا children به همان کامپوننتهایی که قرار است توسط Context.Provider محصور شوند اشاره میکند.
- تابع کمکی useStore، جهت محصور کردن متد React.useContext، اضافه شدهاست. میتوانید useContext Hook را به صورت مستقیم در کامپوننتهای تابعی فراخوانی کنید و یا میتوانید از متد کمکی useStore بجای آن استفاده نمائید تا حجم کدهای تکراری برنامه کاهش یابد.
ج) استفادهی از StoreProvider تهیه شده
اکنون با استفاده از متد StoreProvider فوق که شیء Context.Provider استاندارد React را بازگشت میدهد، میتوان کامپوننتهای بالاترین کامپوننت سلسه مراتب کامپوننتهای برنامه را محصور کرد، تا تمام آنها بتوانند به store ذخیره شدهی در Provider، دسترسی پیدا کنند:
export default function App() { return ( <StoreProvider> <main> <Component1 /> <Component2 /> <Component3 /> </main> </StoreProvider> ); }
د) استفاده از store مهیا شده در کامپوننتهای تابعی برنامه
پس از تهیهی متدی کمکی useStore که در حقیقت همان useContext Hook است، میتوان به کمک آن در کامپوننتهای تابعی، به store و تمام امکانات آن دسترسی پیدا کرد:
const store = useStore();
سؤال: آیا هنوز هم میتوان یک مخزن پیچیدهی متشکل از چندین کلاس را تشکیل داد؟
پاسخ: بله. برای مثال ابتدا دو کلاس جدید CounterStore و ThemeStore را به نحو متداولی، با استفادهی از MobX decorators طراحی میکنیم (دقیقا مانند مثال قسمت قبل). سپس بجای ذکر نال، بجای پارامتر متد createContext، آنرا با یک شیء جدید مقدار دهی میکنیم که هر کدام از خواص آن، به یک وهله از مخازن حالت ایجاد شده اشاره میکند:
export const storesContext = React.createContext({ counterStore: new CounterStore(), themeStore: new ThemeStore(), }); export const useStores = () => React.useContext(storesContext);
const { counterStore } = useStores();
چند نکتهی تکمیلی
نکته 1: با اشیاء MobX از Object Destructuring استفاده نکنید!
اگر بر روی اشیاء MobX از Object Destructuring استفاده کنیم، خروجی آن تبدیل به متغیرهای سادهای خواهند شد که دیگر ردیابی نمیشوند.
برای مثال اگر counterStore مثال فوق به همراه خاصیت observable ای به نام activeUserName است، آنرا به صورت زیر تبدیل به متغیر activeUserName نکنید؛ چون دیگر reactive نخواهد بود:
const { counterStore: { activeUserName }, } = useStores();
const { counterStore } = useStores();
نکته 2: مدیریت side effects با MobX
در مورد اثرات جانبی و side effects در مطلب «قسمت 32 - React Hooks - بخش 3 - نکات ویژهی برقراری ارتباط با سرور» بیشتر بحث شد. اگر یک اثر جانبی مانند تنظیم document.title، به مقدار یک خاصیت observable وابسته بود، میتوان از متد autorun که تغییرات آنها را ردیابی میکند، درون useEffect Hook استاندارد، استفاده کرد:
import React from 'react' import { autorun } from 'mobx' function useDocumentTitle(store) { React.useEffect( () => autorun(() => { document.title = `${store.title} - ${store.sectionName}` }), [], // note empty dependencies ) }
نکته 4: روش فعالسازی MobX strict mode
اگر strict mode را در Mobx به روش زیر فعال کنیم:
import { configure } from "mobx"; configure({ enforceActions: true });
نکته 3: روش انجام اعمال async در MobX
فرض کنید یک عملیات async را در یک اکشن متد کلاس حالت MobX، به صورت زیر انجام دادهایم و نتیجهی آن به خاصیت weatherData آن کلاس که observable است، به صورت مستقیم انتساب داده شدهاست:
@action loadWeather = city => { fetch( `https://abnormal-weather-api.herokuapp.com/cities/search?city=${city}` ) .then(response => response.json()) .then(data => { this.weatherData = data; }); };
راه حل اول: تغییر خاصیت this.weatherData را به یک اکشن متد مجزا انتقال میدهیم:
@action setWeather = data => { this.weatherData = data; };
راه حل دوم: اگر نمیخواهیم یک اکشن متد جدید را تعریف کنیم، میتوان از متد کمکی runInAction در داخل یک callback استفاده کرد:
loadWeatherInline = city => { fetch(`http://jsonplaceholder.typicode.com/comments/${city}`) .then(response => response.json()) .then(data => { runInAction(() => (this.weatherData = data)); }); };
در مورد اعمال async/await چطور؟
در اینجا هم تفاوتی نمیکند. هر چیزی پس از await، شبیه به حالت متد then پردازش میشود. به همین جهت در اینجا نیز باید از یکی از دو راه حل ارائه شده، استفاده کرد:
loadWeatherAsync = async city => { const response = await fetch( `http://jsonplaceholder.typicode.com/comments/${city}` ); const data = await response.json(); runInAction(() => { this.weatherData = data; }); };
مطالب
MongoDB #12
ایندکس گذاری در MongoDB
ایندکسها تاثیر بسیاری در اجرای کوئریها دارند. بدون ایندکسها، MongoDB باید تمام سندهای یک مجموعه را برای انتخاب سندهایی که با عبارت کوئری مطابقت دارند، اسکن کند. این اسکن بسیار ناکارآمد است و در این حالت mongoDB به پردازش حجم بزرگی از دادهها نیاز دارد.
ایندکسها ساختارهای دادهی مخصوصی هستند که بخش کوچکی از مجموعه دادهها را به شکل سادهای برای پیمایش، ذخیره میکنند. ایندکس، مقدار فیلد یا فیلدهای خاصی را که بعنوان ایندکس تعیین شدهاند، ذخیره میکند.
متد ()ensureIndex
برای ساخت یک ایندکس باید از متد ()ensureIndex در MongoDB استفاده کنید.
گرامر
گرامر پایه متد ()ensureIndex به شکل زیر است:
>db.COLLECTION_NAME.ensureIndex({KEY:1})
در اینجا، key نام فیلدی است که میخواهید بر روی آن ایندکس بسازید و مقدار 1 برای مرتب سازی بصورت صعودی است. برای ساخت ایندکسهایی با مرتب سازی نزولی باید از مقدار 1- استفاده کنید.
مثال
>db.mycol.ensureIndex({"title":1})
در متد ()ensureIndex برای ساخت ایندکس بر روی چندین فیلد، میتوانید چندین فیلد را به آن پاس دهید:
>db.mycol.ensureIndex({"title":1,"description":-1})
متد ()ensureIndex همچنین یک لیست از اختیارات را قبول میکند که در ادامه آمدهاند:
پارامتر | نوع داده | توضیحات |
background | Boolean | ایندکس را در پس زمینه میسازد؛ بنابراین عمل ساخت ایندکس، بقیه فعالیتهای پایگاه داده را مسدود یا متوقف نمیکند. برای این کار مقدار را true تعیین کنید. مقدار پیش فرض false است. |
unique | Boolean | یک ایندکس یکتا را میسازد. در این حالت مجموعه، اجازه درج سندهایی را که مقدار کلید یا کلیدهای آنها از قبل وجود دارند، نخواهد داد. برای ساخت یک ایندکس یکتا مقدار را true تعیین کنید. مقدار پیش فرض آن false است. |
name | string | نام ایندکس. اگر تعیین نشود MongoDB نام خودکاری را توسط الحاق نام فیلدها و ترتیب مرتب سازی تولید میکند. |
dropDups | Boolean | ایندکسی را بر روی فایل میسازد که ممکن است مقادیر تکراری داشته باشد. MongoDB فقط اولین پیشامد از یک کلید را ایندکس میکند و همه سندهایی را که پیشامد ثانویه کلید هستند، از مجموعه حذف میکند. برای ساخت ایندکس یکتا مقدار را true تعیین کنید. مقدار پیش فرض آن false است. |
sparse | Boolean | اگر مقدار آن true تعیین شود، ایندکس فقط به سندهایی با فیلد تعیین شده رجوع میکند. این ایندکسها از فضای کمی استفاده کرده و در برخی موقعیتها متفاوت رفتار میکنند. مقدار پیش فرض آن false است. |
expireAfterSeconds | integer | تعیین یک مقدار به ثانیه بعنوان TTL، برای کنترل کردن اینکه چه مدت MongoDB اسناد را در این مجموعه نگه دارد. |
v | index version | شمارهی نسخه ایندکس. نسخهی ایندکس پیش فرض بستگی به نسخه mongod درحال اجرای هنگام ساخت ایندکس دارد. |
weights | document | وزن یک عدد بین 1 تا 99999 است و مشخص کنندهی اهمیت فیلد با دیگر فیلدهای ایندکس شده از نظر امتیاز است. |
default_language | string | برای یک ایندکس متنی، زبانی برای تعیین کردن لیست کلمات متوقف کننده و نقش هایی برای ریشهیابی و نشانه گذاری کلمات. |
language_override | string | برای یک ایندکس متنی، تعیین کننده نام فیلد در سند که شامل زبانی برای لغو کردن زبان پیش فرض است. مقدار پیش فرض آن language است. |