بهترین شیوه‌های مدیریت state ها در انگولار

بهترین شیوه‌ها عمدتاً به ترجیحات شخصی مربوط می‌شوند و افرادی با نظرات متفاوت می‌توانند با آن ها مخالفت کنند.همانطور که گفته شد، بهترین روش ها در این مقاله بر اساس یک دهه کار با برنامه های کاربردی تک صفحه ای و مدیریت وضعیت ها است.من در 7 سال گذشته در بیش از 100 پروژه Angular حضور داشته ام و هزاران روش مختلف را دیده ام و طرز فکر صدها و صدها متخصص مختلف را یاد گرفته ام.من flux، redux، state models، @ngrx/store، Akita، Ngxs، BehaviorSubjects، ObservableState، Signals را انجام داده ام... چیزهای زیادی دیده ام و این مقاله درباره نحوه استدلال من در مورد حالت است.مدیریت state چیست ؟این ممکن است یک سوال پیش پا افتاده به نظر برسد اما نظرات مختلفی در مورد این موضوع وجود دارد. اولین سوالی که می خواهیم از خود بپرسیم این است: "state چیست؟". "وضعیت یک مقدار یا شی است که در حافظه نگهداری می شود".یک property روی یک component که دارای یک مقدار است، حالت در نظر گرفته می شود.یک property روی یک service که دارای یک مقدار است، حالت در نظر گرفته می شود.مقدار یک ویژگی ()Input@ حالت در نظر گرفته می شود.مقداری که در نوعی store در چارچوب مدیریت state مانند ngrx@ نگهداری می شود، حالت در نظر گرفته می شود.مقداری که در سطح ngModule یا سطح route ارائه می شود، حالت در نظر گرفته می شود.@Component({...} export class HelloCompontent { // This message property is state public message = 'hello'; }حالا مدیریت state چیست؟ زمانی در مورد مدیریت state صحبت خواهیم کرد که یک یا چند مورد از این گفته ها درست باشد:وقتی می خواهیم حالت را به روز کنیم.وقتی می خواهیم حالت را اضافه کنیم.وقتی می خواهیم حالت را باطل کنیم.وقتی می خواهیم حالت را بین کامپوننت ها اشتراک بگذاریم.وقتی می خواهیم حالت را بین فیچر ها به اشتراک بگذاریم.وقتی می‌خواهیم حالت را در db، localstorage و غیره حفظ کنیم.اولین تصور اشتباه :بسیاری از توسعه دهندگان فکر می کنند که مدیریت state به این معنی است که: به اشتراک گذاری وضعیت در سراسر برنامه. این درست نیست، و با این استدلال در مورد state مردم تمایل دارند تا وضعیت های زیادی را مدیریت کنند یا آن را اشتباه مدیریت کنند.مدیریت state به سادگی مثال زیر است :@Component({...} export class HelloCompontent { // This message property is state public message = 'hello'; // This is state management public update(newMessage: string): void { this.message = newMessage; } }دومین تصور اشتباه :اینکه آیا شما عاشق ایجاد اکشن‌ها برای هر چیزی هستید که باعث ایجاد افکت‌ها می‌شود، که اینها نیز باعث ایجاد افکت‌های دیگری می‌شوند که دوباره باعث ایجاد افکت‌های دیگر می‌شوند، تصمیمی است که هر تیم باید برای خود بگیرد.اما بیایید وانمود نکنیم که این مدیریت state است.بهترین شیوه شماره 1: KISS (Keep it simple, stupid)هنگامی که این اصل برای اکثر راه حل ها در توسعه نرم افزار (یا حتی در زندگی) معتبر است، بسیار مهم است که در مدیریت state نیز این ساده نگه داشتن را رعایت کنید.انگولار از مدیریت state مبتنی بر RxJS به سمت مدیریت حالت سیگنال محور حرکت می کند.این پیام بسیار واضحی را ارسال می کند: تیم اصلی انگولار می خواهد مدیریت حالت را ساده تر کند.بهترین شیوه شماره 2: حالتی را که نباید مدیریت کنید، مدیریت نکنید.این به این خلاصه می شود: همه چیز را در یک store قرار ندهید. اگر انجام دهید:برای به دست آوردن آن حالت، باید از حلقه ها بپرید.با کدهای تکراری و هزینهنگهداری زیادی مواجه خواهید شد.شما باید آن حالت را در یک زمان معین باطل کنید.شما تمام ماژول های feature برنامه خود را به هم می چسبانید.شما با یک طراحی store پیچیده به پایان خواهید رسید.این مثال را در نظر بگیرید،آیا این کد ساده تر از این نمی تواند باشد؟!‍‍‍@Component({ ... template: ` <user-form [user]=&quotuser$|async&quot></user-form> ` }) export class UserComponent { ... public user$ = this.userId$.pipe( switchMap(id =>this.userService.getById(id)) ); }این مثال ساده:هر بار که $userId تغییر می کند، داده ها را برای ما واکشی می کند.به طور خودکار برای ما subscribes می کند.داده ها را به صورت خودکار برای ما در هنگام subscription واکشی می کند.به طور خودکار برای ما unsubscribes می کند.برای مثال، با قرار دادن آن در store در ngrx@ ، پیچیدگی های زیر را داریم:یک reducers , selector , effects , actionType , action باید نوشته شوند.ما مستقیماً به پاسخ ()getById دسترسی نداریم، باید آن را در store اضافه کنیم.ما مستقیماً به خطاها دسترسی نداریم، اگر می‌خواهیم به آنها دسترسی پیدا کنیم، باید آن‌ها را در store اضافه کنیم.وقتی کامپوننت از بین می‌رود، باید user را باطل کنیم، بنابراین در متد ()ngOnDestroy باید این کار را انجام دهیم.قانون کلی این است: اگر حالت شما نیازی به اشتراک گذاری ندارد، آن را در store قرار ندهید.بهترین شیوه شماره 3: state خود را تا حد امکان پایین نگه دارید.منظور ما از آن این است که اگر به حالت خود در پایین ترین مؤلفه فرزند نیاز دارید، و فقط در آنجا... آن را همانجا نگه دارید!اگر می‌خواهید یک حالت را بین دو مؤلفه خواهر و برادر به اشتراک بگذارید، وضعیت را در مؤلفه والد این دو نگه دارید.اگر نیاز دارید که یک حالت را بین smart components به اشتراک بگذارید، state ارائه شده را در feature حفظ کنید.اگر واقعاً نیاز به یک قسمت از state دارید که بین ماژول‌های مختلف لود شده تنبل به اشتراک گذاشته شود، در آن صورت، ما در مورد global state صحبت می‌کنیم و این بالاترین سطح است. همیشه سعی کنید آن را تا حد امکان پایین نگه دارید. در آینده از خود تشکر خواهید کرد!انگولار دارای این ویژگی تزریق وابستگی عالی است که در آن ما می توانیم یک تزریق را در انواع مختلف سطوح ارائه کنیم. به عنوان مثال به این مثال توجه کنید:@Component({ ... // provided right on this ChatboxComponent providers: [ChatboxState] }) export class ChatboxComponent { private readonly state = inject(ChatboxState); }اکنون ChatboxState برای ChatboxComponent مهیا شده است و چرخه عمر آن مؤلفه را نیز به اشتراک می‌گذارد.این بدان معناست که وقتی ChatboxComponent از بین می‌رود، نمونه ChatboxState نیز از بین می‌رود. حتی می توانید قلاب چرخه حیات ()ngOnDestroy را در ChatboxState نیز پیاده سازی کنید:export class ChatboxState implements OnDestroy { // gets called when the item that provides this class gets destroyed public ngOnDestroy(): void { } }هرچه این state را پایین تر نگه دارید، مدیریت آن برای ما آسان تر خواهد شد. در این سناریو، به عنوان مثال، بی اعتباری حالت به صورت رایگان اتفاق می افتد.بهترین شیوه شماره 4: حالت را synchronous نگه دارید، همیشه snapshots ارائه دهید.طرز تفکر سالم در مورد state این است که state همیشه باید یک مقدار داشته باشد. این بدان معناست که ما همیشه نمی‌خواهیم subscribe کنیم، اما می‌خواهیم یک snapshots از آن مقدار دریافت کنیم.این مثال بد را در نظر بگیرید:public saveUser(): void { this.user$ .pipe( take(1), withLatestFrom(this.courses$), takeUntil(this.destroy$) ) .subscribe(({user, courses}) => { this.userService.update(user, courses).subscribe({...}) }) }از آنجا که $user و $courses هر دو observable هستند، نمی‌توانیم فقط مقادیری از آنها دریافت کنیم. برای بازیابی آن مقادیر، باید subscribe کنیم. اگر حالت ما synchronous باشد، این قطعه کد بسیار تمیزتر می شود:public saveUser(): void { const {user, courses} = this.state.snapshot; this.userService.update(user, courses).subscribe({...}) }آن قطعه کد از ObservableState استفاده می‌کند، اما سیگنال‌ها نیز در مورد آن هستند. با سیگنال ها به شکل زیر است:public saveUser(): void { const {user, courses} = this.state(); this.userService.update(user, courses).subscribe({...}) }بهترین شیوه شماره 5: همیشه مقادیر اولیه را ارائه دهید.هنگام کار با state، این یک راه سالم است که همیشه به مقادیر اولیه فکر کنید: اگر می توانید از مقدار null اجتناب کنید، در برخی موارد به آن نیاز خواهید داشت، اما نمونه سازی یک آرایه با [ ] کد شما را قوی تر می کند.شما خطای "Cannot read properties of null" کمتری خواهید داشت و استدلال در مورد state را آسان تر می کند.نوشتن type برای state نیز آسان تر می شود زیرا مجبور نیستید your-type>|null> را بعد از هر نوع حالت اضافه کنید. این برای مثال می تواند بسیار آزاردهنده باشد:type UserState = { firstName: string|null, lastName: string|null // etc }بهترین شیوه شماره 6: ساختارهای داده تغییرناپذیرساختارهای داده تغییرناپذیر لزوماً به دلیل بهینه سازی عملکرد استفاده نمی شوند. دلیل استفاده از آن این است که این ساختارها قابل پیش بینی هستند. ما می‌توانیم Change Detection را بهینه کنیم، می‌توانیم از عملگرهای RxJS استفاده کنیم و می‌توانیم جریان‌های داده یک طرفه تمیز ایجاد کنیم.بطور خلاصه : چه از چارچوب‌های مدیریت حالت استفاده کنیم یا از پیاده‌سازی‌های سفارشی، سعی کنید آن را ساده نگه دارید، state را تا حد امکان پایین نگه دارید، مطمئن شوید که مقادیر اولیه و snapshots دارید و وضعیتی را که نباید مدیریت کنید، مدیریت نمی‌کنید.لینک مقاله اصلی: https://blog.simplified.courses/angular-state-management-best-practices