- بهبود کپسوله سازی قسمتهای مختلف برنامه با بسته بندی آنها در ماژولهای متفاوت
- فراهم آوردن امکان lazy loading و بهبود کارآیی برنامه
انواع ماژولهای توصیه شدهی در برنامههای Angular
منهای App Module پیشفرض یک برنامههای Angular، ایجاد سه نوع ماژول دیگر نیز در جهت سازماندهی اینگونه برنامهها توصیه میشوند:
- Core Module
هدف از آن فراهم آوردن سرویسهای Singleton اشتراکی بین کامپوننتها و ماژولهای مختلف برنامه است. علت اینجا است که سیستم تزریق وابستگیهای Angular، به ازای هر ماژولی که Lazy loaded باشد، سرویس تزریقی در آنها را مجددا وهله سازی میکند. به همین جهت نیاز است تک ماژول اختصاصی را برای مدیریت سرویسهایی که نیازی است تنها یکبار در طول عمر برنامه وهله سازی شوند، تدارک ببینیم و Core Module مکان مناسبی برای اینکار است.
همچنین Code Module باید شامل کامپوننتهایی در سطح برنامه باشد. دراینجا منظور از «در سطح برنامه»، کامپوننتهایی که قرار است در بین تمام ماژولها به اشتراک گذاشته شوند، نیست. منظور تنها کامپوننتهایی هستند که در App Component اصلی برنامه قرار است استفاده شوند؛ مانند منوی راهبری بالای سایت.
- Shared Module
هدف از آن مدیریت و بسته بندی کامپوننتها، دایرکتیوها و Pipes اشتراکی بین تمام اجزای برنامه است. برای مثال کامپوننت «لطفا منتظر بمانید ...» اگر قرار است در تمام قسمتهای برنامه استفاده شود، نیاز است در Shared Module تعریف شود. از این جهت که در یک برنامهی Angular نمیتوان یک کامپوننت را بین دو ماژول مختلف به اشتراک گذاشت. به همین جهت نیاز است یک مکان مرکزی برای تعریف این کامپوننتهای اشتراکی ایجاد شود و سپس این تک ماژول را در قسمتهای مختلف برنامه، بدون مشکل مورد استفاده قرار داد.
- Feature Module
این ماژولها به ازای هر ویژگی برنامه ایجاد شده و کامپوننتها، سرویسها، دایرکتیوها و Pipes اختصاصی آن ویژگی را بسته بندی میکنند.
ایجاد Core Module
فرض کنید میخواهید اطلاعات کاربر جاری لاگین شده را در طول عمر برنامه نگهداری کنید و از آن در تمام قسمتهای برنامه استفاده نمائید. یک چنین سرویسی نیاز است دارای طول عمر Singleton باشد و تنها یکبار وهله سازی شود تا اطلاعات کاربر جاری از دست نرود. به همین جهت بهترین مکان تعریف این سرویس، در Core Module است.
برای این منظور در ساختار برنامهی خود، پوشهی جدید src\app\core را ایجاد میکنیم. سپس فایل core.module.ts را به صورت ذیل در آن تعریف خواهیم کرد:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { UserRepositoryService } from './user-repository.service'; import { NavBarComponent } from './nav-bar.component'; import { AccountMenuComponent } from './account-menu.component'; @NgModule({ imports: [ CommonModule, RouterModule ], exports: [ NavBarComponent, AccountMenuComponent ], declarations: [ NavBarComponent, AccountMenuComponent ], providers: [ UserRepositoryService ] }) export class CoreModule { };
- سپس سرویسهای اشتراکی و Singleton برنامه در قسمت providers آن قرار میگیرند.
- در اینجا همچنین دو کامپوننت منو که توسط app.component.ts مورد استفاده قرار میگیرند نیز import شدهاند.
- فایلهای account-menu.component.ts، nav-bar.component.ts و user-repository.service.ts نیز به درون پوشهی src\app\core منتقل خواهند شد (به همراه تمام فایلهای html و css متناظر با آنها).
- اگر دقت کنید، قسمت exports این ماژول نیز مقدار دهی شدهاست. چون این کامپوننتها قرار است خارج از این ماژول و در AppModule استفاده شوند، نیاز است آنها را به صورت خروجی نیز معرفی کنیم.
اکنون جهت استفادهی از این قابلیتها، تنها کافی است تعریف CoreModule را به AppModule در فایل app.module.ts اضافه کنیم:
import { CoreModule } from "./core/core.module"; @NgModule({ imports: [ //... CoreModule, //... RouterModule.forRoot(appRoutes) ], //... }) export class AppModule { }
ایجاد Shared Modules
در Shared Module اجزایی را قرار خواهیم داد که قرار است در بیش از یک ماژول مورد استفاده قرار گیرند. به همین جهت در ساختار برنامهی خود، پوشهی جدید src\app\shared را ایجاد میکنیم. سپس در آن، ماژول جدید shared.module.ts را ایجاد خواهیم کرد:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LoadingSpinnerComponent } from './loading-spinner.component'; @NgModule({ imports: [ CommonModule ], declarations: [ LoadingSpinnerComponent ], exports: [ LoadingSpinnerComponent, CommonModule ], providers: [ ] }) export class SharedModule { };
از این جهت که اجزای خروجی این ماژول قرار است در Feature Moduleها استفاده شوند، CommonModule مورد استفادهی در آنها نیز در قسمت exports ذکر شدهاست.
اکنون جهت استفادهی از این قابلیتها، تنها کافی است تعریف SharedModule را به AppModule در فایل app.module.ts اضافه کنیم:
import { CoreModule } from "./core/core.module"; import { SharedModule } from "./shared/shared.module"; @NgModule({ imports: [ //... CoreModule, SharedModule, //... RouterModule.forRoot(appRoutes) ], //... }) export class AppModule { }
ایجاد Feature Modules
این مورد نکتهی ویژهای را به همراه ندارد و همانند ایجاد سایر ماژولهای برنامهاست. برای مثال ویژگی مدیریت کاربران، به همراه تمام اجزای آن درون ماژول کاربران قرار میگیرد و به همین ترتیب برای سایر ویژگیهای دیگر برنامه. ایجاد و مدیریت اینگونه ماژولها توسط Angular CLI بسیار سادهاست:
> ng g m users -m app.module --routing > ng g c users/users-list
دستور دوم نیز کامپوننتی را به این ماژول اضافه میکند؛ به همراه به روز رسانی تعاریف این ماژول.
فقط در اینجا SharedModule ایی را که پیشتر اضافه کردیم، به قسمت imports آن اضافه میکنیم:
import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SharedModule } from '../shared/shared.module'; import { UsersListComponent } from './users-list.component'; @NgModule({ imports: [ RouterModule, SharedModule ], declarations: [ UsersListComponent ], exports: [ ], providers: [ ] }) export class UsersModule { };
In this post, we are going to write about what we consider to be the best practices while developing the .NET Core Web API project. How we can make it better and how to make it more maintainable.
We are going to go through the following sections:
مقایسهی Blazor و Angular
چرا به ابزارهای مدیریت حالت نیاز داریم؟
به محض رد شدن از مرز پیاده سازی امکانات اولیهی یک برنامه، نیاز به ابزارهای مدیریت حالت نمایان میشوند؛ خصوصا زمانیکه نیاز است با اطلاعات قابل توجهی سر و کار داشت. مهمترین دلیل استفادهی از یک ابزار مدیریت حالت، مدیریت منطق تجاری برنامه است. منطق نمایشی برنامه مرتبط است به نحوهی نمایش اجزای آن در صفحه؛ مانند نمایش یک صفحهی مودال، تغییر رنگ عناصر با عبور کرسر ماوس از روی آنها و در کل منطقی که مرتبط و یا وابستهی به هدف اصلی برنامه نیست. از سوی دیگر منطق تجاری برنامه مرتبط است با مدیریت، تغییر و ذخیره سازی اشیاء تجاری مورد نیاز آن؛ مانند اطلاعات حساب کاربری شخص و دریافت اطلاعات برنامه از یک API که مختص به برنامهی خاص ما است و به همین دلیل نیاز به ابزاری برای مدیریت بهینهی آن وجود دارد. برای مثال اینکه در کجا باید منطق تجاری و نمایشی را به هم متصل کرد، میتواند چالش بر انگیر باشد. چگونه باید اطلاعات کاربر را ذخیره کرد؟ چگونه React باید متوجه شود که اطلاعات ما تغییر کردهاست و در نتیجهی آن کامپوننتی را مجددا رندر کند؟ یک ابزار مدیریت حالت، تمام این مسایل را به نحو یکدستی در سراسر برنامه، مدیریت میکند.
اگر از یک ابزار مدیریت حالت استفاده نکنیم، مجبور خواهیم شد تمام اطلاعات منطق تجاری را در داخل state کامپوننتها ذخیره کنیم که توصیه نمیشود؛ چون مقیاس پذیر نیست. برای مثال فرض کنید قرار است تمام اطلاعات state را داخل یک کامپوننت ذخیره کنیم. هر زمانیکه بخواهیم این state را از طریق یک کامپوننت فرزند تغییر دهیم، نیاز خواهد بود این اطلاعات را به والد آن کامپوننت ارسال کنیم که اگر از تعداد زیادی کامپوننت تو در تو تشکیل شده باشد، زمانبر و به همراه کدهای تکراری زیادی خواهد بود. همچنین اینکار سبب رندر مجدد کل برنامه با هر تغییری در state آن میشود که غیرضروری بوده و کارآیی برنامه را کاهش میدهد. به علاوه در این بین مشخص نیست هر قسمت از state، از کدام کامپوننت تامین شدهاست. به همین جهت نیاز به روشی برای مدیریت حالت در بین کامپوننتهای برنامه وجود دارد.
داشتن تنها یک محل برای ذخیره سازی state در برنامه
همانطور که در قسمت 8 ترکیب کامپوننتها در سری React 16x بررسی کردیم، هر کامپوننت در React، دارای state خاص خودش است و این state از سایر کامپوننتها کاملا مستقل و ایزولهاست. این مورد با بزرگتر شدن برنامه و برقراری ارتباط بین کامپوننتها، مشکل ایجاد میکند. برای مثال اگر بخواهیم دکمهای را در صفحه قرار داده و توسط این دکمه درخواست صفر شدن مقدار هر کدام از شمارشگرها را صادر کنیم، با صفر کردن value هر کدام از این کامپوننتها، اتفاقی رخ نمیدهد. چون state محلی این کامپوننتها، با سایر اجزای صفحه به اشتراک گذاشته نمیشود و باید آنرا تبدیل به یک controlled component کرد، بطوریکه دارای local state خاص خودش نیست و تمام دادههای دریافتی را از طریق this.props دریافت میکند و هر زمانیکه قرار است دادهای تغییر کند، رخدادی را به والد خود صادر میکند. بنابراین این کامپوننت به طور کامل توسط والد آن کنترل میشود. تازه این روش در مورد کامپوننتهایی صدق میکند که رابطهی والد و فرزندی بین آنها وجود دارد. اگر چنین رابطهای وجود نداشت، باید state را به یک سطح بالاتر انتقال داد. برای مثال باید state کامپوننت Counters را به والد آن که کامپوننت App است، منتقل کرد. پس از آن چون کامپوننتهای ما، از کامپوننت App مشتق میشوند، اکنون میتوان این state را به تمام فرزندان App توسط props منتقل کرد و به اشتراک گذاشت. این مورد هم مانند مثال انتقال اطلاعات کاربر لاگین شدهی به سیستم، به تمام زیر قسمتهای برنامه، نیاز به ارسال اطلاعات از طریق props یک کامپوننت، به کامپوننت بعدی را دارد و به همین ترتیب برای مابقی که به props drilling مشهور است و روش پسندیدهای نیست.
Redux چیست؟ ذخیره سازی کل درخت state یک برنامه، در یک محل. به این ترتیب به یک شیء جاوا اسکریپتی بزرگ خواهیم رسید که در برگیرندهی تمام state برنامهاست. یکی از مزایای آن امکان serialize و deserialize کل این شیء، به سادگی است. برای مثال توسط متد JSON.stringify میتوان آنرا در جائی ذخیره کرد و سپس آنرا به صورت یک شیء جاو اسکریپتی در زمانی دیگر بازیابی کرد. یکی از مزایای آن، امکان بازیابی دقیق شرایط کاربری است که دچار مشکل شدهاست و سپس دیباگ و رفع مشکل او، در زمانی دیگر.
تاریخچهای از سیستمهای مدیریت حالت
همه چیز با AngularJS 1x شروع شد که از data binding دو طرفه پشتیبانی میکرد. هرچند این روش برای همگام نگه داشتن View و مدل برنامه، مفید است، اما در Viewهای پیچیده، برنامه را کند میکند. در همین زمان فیسبوک، روش مدیریت حالتی را به نام Flux ارائه داد که از data binding یک طرفه پشتیبانی میکرد. به این معنا که در این روش، همواره اطلاعات از View به مدل، جریان پیدا میکند. کار کردن با آن سادهاست؛ چون نیازی نیست حدس زده شود که اکنون جریان اطلاعات از کدام سمت است. اما مشکل آن عدم هماهنگی model و view، در بعضی از حالات است. Flux از این جهت به وجود آمد که مدیریت حالت در برنامههای React آن زمان، پیچیده بود و مقیاس پذیری کمی داشت (پیش از ارائهی Context و Hooks). در کل Flux صرفا یکسری الگوی مدیریت حالت را بیان میکند و یک کتابخانهی مجزا نیست. بر مبنای این الگوها و قراردادها، میتوان کتابخانههای مختلفی را ایجاد کرد. از این رو در سال 2015، کتابخانههای زیادی مانند Reflux, Flummox, MartyJS, Alt, Redux و غیره برای پیاده سازی آن پدید آمدند. در این بین، کتابخانهی Redux ماندگار شد و پیروز این نبرد بود!
توابع خالص و ناخالص (Pure & Impure Functions)
پیش از شروع بحث، نیاز است با یکسری از واژهها مانند توابع خالص و ناخالص آشنا شد. این نکات از این جهت مهم هستند که Redux فقط با توابع خالص کار میکند.
توابع خالص: تعدادی آرگومان را دریافت کرده و بر اساس آنها، مقداری را باز میگردانند.
// Pure const add = (a, b) => { return a + b; }
توابع ناخالص: این نوع توابع سبب تغییراتی در متغیرهایی خارج از میدان دید خود میشوند و یا به همراه یک سری اثرات جانبی (side effects) مانند تعامل با دنیای خارج (وجود یک console.log در آن تابع و یا دریافت اطلاعاتی از یک API خارجی) هستند.
// Impure const b; const add = (a) => { return a + b; }
// Impure const add = (a, b) => { console.log('lolololol'); return a + b; }
// Impure const add = (a, b) => { Api.post('/add', { a, b }, (response) => { // Do something. }); };
روشهایی برای جلوگیری از تغییرات در اشیاء در جاوا اسکریپت
ایجاد تغییرات در آرایهها و اشیاء (Mutating arrays and objects) نیز ناخالصی ایجاد میکند؛ از این جهت که سبب تغییراتی در دنیای خارج (خارج از میدان دید تابع) میشویم. به همین جهت نیاز به روشهایی وجود دارد که از این نوع تغییرات جلوگیری کرد:
// Copy object const original = { a: 1, b: 2 }; const copy = Object.assign({}, original);
برای مثال در React، برای انجام رندر نهایی، در پشت صحنه کار مقایسهی اشیاء صورت میگیرد. به همین جهت اگر همان شیءای را که ردیابی میکند تغییر دهیم، دیگر نمیتواند به صورت مؤثری فقط قسمتهای تغییر کردهی آنرا تشخیص داده و کار رندر را فقط بر اساس آنها انجام دهد و مجبور خواهد شد کل یک شیء را بارها و بارها رندر کند که اصلا بهینه نیست. به همین جهت، ایجاد تغییرات مستقیم در شیءای که به state آن انتساب داده میشود، مجاز نیست.
متد Object.assign، چندین شیء را نیز میتواند با هم یکی کند و شیء جدیدی را تشکیل دهد:
// Extend object const original = { a: 1, b: 2 }; const extension = { c: 3 }; const extended = Object.assign({}, original, extension);
// Copy object const original = { a: 1, b: 2 }; const copy = { ...original };
// Extend object const original = { a: 1, b: 2 }; const extension = { c: 3 }; const extended = { ...original, ...extension };
روشهایی برای جلوگیری از تغییرات در آرایهها در جاوا اسکریپت
متد slice آرایهها نیز بدون ذکر آرگومانی، یک کپی از آرایهی اصلی را ایجاد میکند:
// Copy array const original = [1, 2, 3]; const copy = [1, 2, 3].slice();
// Copy array const original = [1, 2, 3]; const copy = [ ...original ];
// Extend array const original = [1, 2, 3]; const extended = original.concat(4); const moreExtended = original.concat([4, 5]);
معادل قطعه کد فوق در ES-6 و به همراه spread operator آن به صورت زیر است:
// Extend array const original = [1, 2, 3]; const extended = [ ...original, 4 ]; const moreExtended = [ ...original, ...extended, 5 ];
مفاهیم ابتدایی Redux
در Redux برای ایجاد تغییرات در شیء کلی state، از مفهومی به نام dispatch actions استفاده میشود. action در اینجا به معنای رخدادن چیزی است؛ مانند کلیک بر روی یک دکمه و یا دریافت اطلاعاتی از یک API. در این حالت مقایسهای بین وضعیت قبلی state و وضعیت فعلی آن صورت میگیرد و تغییرات مورد نیاز جهت اعمال به UI، محاسبه خواهند شد.
اصلیترین جزء Redux، تابعی است به نام Reducer. این تابع، یک تابع خالص است و دو آرگومان را دریافت میکند:
تابع Reducer، بر اساس action و یا رخدادی، ابتدا کل state برنامه را دریافت میکند و سپس خروجی آن بر اساس منطق این تابع، یک state جدید خواهد بود. اکنون که این state جدید را داریم، برنامهی React ما میتواند به تغییرات آن گوش فرا داده و بر اساس آن، UI را به روز رسانی کند. به این ترتیب کار اصلی مدیریت state، به خارج از برنامهی React منتقل میشود.
در این تصویر، تابع action creator را هم ملاحظه میکند که کاملا اختیاری است. یک action میتواند یک رشته و یا یک عدد باشد. با پیچیده شدن برنامه، نیاز به ارسال یکسری متادیتا و یا اطلاعات بیشتری از اکشن رسیدهاست. کار action creator، ایجاد شیء action، به صورت یک دست و یکنواخت است تا دیگر نیازی به ایجاد دستی آن نباشد.
مزایای کار با Redux
- داشتن یک مکان مرکزی برای ذخیره سازی کلی حالت برنامه (به آن «source of truth» و یا store هم گفته میشود): به این ترتیب مشکل ارسال خواص در بین کامپوننتهای عمیق و چند سطحی، برطرف شده و هر زمانیکه نیاز بود، از آن اطلاعاتی را دریافت و یا با قالب خاصی، آنرا به روز رسانی میکنند.
- رسیدن به بهروز رسانیهای قابل پیش بینی state: هرچند در حالت کار با Redux، یک شیء بزرگ جاوا اسکریپتی، کل state برنامه را تشکیل میدهد، اما امکان کار مستقیم با آن و تغییرش وجود ندارد. به همین جهت است که برای کار با آن، باید رویدادی را از طریق actionها به تابع Reducer آن تحویل داد. چون Reducer یک تابع خالص است، با دریافت یک سری ورودی مشخص، همواره یک خروجی مشخص را نیز تولید میکند. به همین جهت قابلیت ضبط و تکرار را پیدا میکند؛ همان بحث serialize و deseriliaze، توسط ابزاری مانند: logrocket. به علاوه قابلیت undo و redo را نیز میتوان به این ترتیب پیاده سازی کرد (state جدید محاسبه شده، مشخص است، کل state قبلی را نیز داریم یا میتوان ذخیره کرد و سپس برای undo، آنرا جایگزین state جدید نمود). افزونهی redux dev tools نیز قابلیت import و export کل state را به همراه دارد.
- چون تابع Reducer، یک تابع خالص است و همواره خروجیهای مشخصی را به ازای ورودیهای مشخصی، تولید میکند، آزمایش کردن، پیاده سازی و حتی logging آن نیز سادهتر است. در این بین حتی یک افزونهی مخصوص نیز برای دیباگ آن تهیه شدهاست: redux-devtools-extension. تابع خالص، تابعی است که به همراه اثرات جانبی نیست (side effects)؛ به همین جهت عملکرد آن کاملا قابل پیش بینی بوده و آزمون پذیری آن به دلیل نداشتن وابستگیهای خارجی، بسیار بالا است.
Context API خود React چطور؟
در قسمت 33 سری React 16x، مفهوم React Context را بررسی کردیم. پس از معرفی آن با React 16.3، مقالات زیادی منتشر شدند که ... Redux مردهاست (!) و یا بجای Redux از React context استفاده کنید. اما واقعیت این است که React Redux در پشت صحنه از React context استفاده میکند و تابع connect آن دقیقا به همین زیر ساخت متصل میشود.
کار با Redux مزایایی مانند کارآیی بالاتر، با کاهش رندرهای مجدد کامپوننتها، دیباگ سادهتر با افزونههای اختصاصی و همچنین سفارشی سازی، مانند نوشتن میانافزارها را به همراه دارد. اما شاید واقعا نیازی به تمام این امکانات را هم نداشته باشید؛ اگر هدف، صرفا انتقال سادهتر اطلاعات بوده و برنامهی مدنظر نیز کوچک است. React Context برخلاف Redux، نگهدارندهی state نیست و بیشتر هدفش محلی برای ذخیره سازی اطلاعات مورد استفادهی در چندین و چند کامپوننت تو در تو است. هرچند شبیه به Redux میتوان اشارهگرهایی از متدها را به استفاده کنندگان از آن ارسال کرد تا سبب بروز رویدادها و اکشنهایی در کامپوننت تامین کنندهی Contrext شوند (یا یک کتابخانهی ابتدایی شبیه به Redux را توسط آن تهیه کرد). بنابراین برای انتخاب بین React Context و Redux باید به اندازهی برنامه، تعداد نفرات تیم، آشنایی آنها با مفاهیم Redux دقت داشت.
In September of 2014, at the ng-Europe conference, the Angular team announced what would be known as Angular 2.Is was a drastic change that left many developers angry. One of the main sources of complaint was the lack of a migration path.
کتاب LINQ مختصر و مفید
Learn to use LINQ to simplify the process of coding and querying in C# and Visual Basic. LINQ Succinctly will guide you through the process, from conceptual understanding to practical implementation. With the help of author Jason Roberts, you will be streamlining your coding and querying practices in no time.