NgRx Avantajlarına ve Özelliklerine Derin Bir Bakış
Yayınlanan: 2022-03-11Bir ekip lideri, bir geliştiriciye belirli bir sorunu çözmek için birkaç yöntem yazmak yerine çok sayıda ortak kod yazmasını söylerse, ikna edici argümanlara ihtiyaçları vardır. Yazılım mühendisleri problem çözücülerdir; işleri otomatikleştirmeyi ve gereksiz kalıplardan kaçınmayı tercih ederler.
NgRx bazı ortak kodlarla birlikte gelse de, geliştirme için güçlü araçlar da sağlar. Bu makale, kod yazmaya biraz daha fazla zaman ayırmanın, çabaya değecek faydalar sağlayacağını göstermektedir.
Çoğu geliştirici, Dan Abramov Redux kitaplığını yayınladığında durum yönetimini kullanmaya başladı. Bazıları devlet yönetimini bir trend olduğu için kullanmaya başladı, yoksun oldukları için değil. Devlet yönetimi için standart bir "Merhaba Dünya" projesi kullanan geliştiriciler, kendilerini aynı kodu tekrar tekrar yazarken hızla bulabilirler, bu da hiçbir kazanç elde etmeden karmaşıklığı artırır.
Sonunda, bazıları hüsrana uğradı ve devlet yönetimini tamamen terk etti.
NgRx ile İlk Problemim
Bence bu ortak endişe, NgRx ile ilgili önemli bir sorundu. İlk başta, arkasındaki büyük resmi göremedik. NgRx bir kütüphanedir, bir programlama paradigması veya zihniyet değildir. Ancak bu kütüphanenin işlevselliğini ve kullanılabilirliğini tam olarak kavrayabilmemiz için bilgimizi biraz daha genişletmemiz ve fonksiyonel programlamaya odaklanmamız gerekiyor. İşte o zaman ortak kod yazabilir ve bundan mutlu olabilirsiniz. (Ciddiyim.) Bir zamanlar NgRx şüphecisiydim; şimdi bir NgRx hayranıyım.
Bir süre önce devlet yönetimini kullanmaya başladım. Yukarıda açıklanan ortak kalıp deneyiminden geçtim, bu yüzden kitaplığı kullanmayı bırakmaya karar verdim. JavaScript'i sevdiğimden, bugün kullanılan tüm popüler çerçeveler hakkında en azından temel bir bilgi edinmeye çalışıyorum. İşte React'i kullanırken öğrendiklerim.
React'in Hooks adında bir özelliği var. Angular'daki Components gibi, Hook'lar da argümanları kabul eden ve değerler döndüren basit fonksiyonlardır. Bir kanca, yan etki olarak adlandırılan bir duruma sahip olabilir. Örneğin, Angular'daki basit bir düğme aşağıdaki gibi React'e çevrilebilir:
@Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }Gördüğünüz gibi, bu basit bir dönüşüm:
- SimpleButtonComponent => SimpleButton
- @Input() name => props.name
- şablon => dönüş değeri
React işlevimiz SimpleButton , işlevsel programlama dünyasında önemli bir özelliğe sahiptir: Bu, saf bir işlevdir . Bunu okuyorsanız, bu terimi en az bir kez duyduğunuzu varsayıyorum. NgRx.io, Anahtar Kavramlarda iki kez saf işlevlerden bahseder:
- Durum değişiklikleri, yeni bir durumu hesaplamak için mevcut durumu ve en son eylemi alan
reducersadı verilen saf işlevler tarafından işlenir. - Seçiciler, durum parçalarını seçmek, türetmek ve oluşturmak için kullanılan saf işlevlerdir .
React'te geliştiriciler, Hook'ları olabildiğince saf işlevler olarak kullanmaya teşvik edilir. Angular ayrıca geliştiricileri Smart-Dumb Component paradigmasını kullanarak aynı modeli uygulamaya teşvik eder.
İşte o zaman bazı önemli fonksiyonel programlama becerilerimden yoksun olduğumu fark ettim. NgRx'i kavramam uzun sürmedi, çünkü işlevsel programlamanın temel kavramlarını öğrendikten sonra bir “Aha! an”: NgRx anlayışımı geliştirdim ve sunduğu faydaları daha iyi anlamak için onu daha fazla kullanmak istedim.
Bu makale, öğrenme deneyimimi ve NgRx ve fonksiyonel programlama hakkında edindiğim bilgileri paylaşıyor. NgRx için API'yi veya eylemlerin nasıl çağrılacağını veya seçicilerin nasıl kullanılacağını açıklamıyorum. Bunun yerine, NgRx'in harika bir kütüphane olduğunu neden takdir ettiğimi paylaşıyorum: Bu sadece nispeten yeni bir trend değil, birçok fayda sağlıyor.
Fonksiyonel programlama ile başlayalım.
Fonksiyonel Programlama
İşlevsel programlama, diğer paradigmalardan büyük ölçüde farklı olan bir paradigmadır. Bu, birçok tanım ve yönerge içeren çok karmaşık bir konudur. Bununla birlikte, işlevsel programlama bazı temel kavramları içerir ve bunları bilmek, NgRx'te (ve genel olarak JavaScript'te) uzmanlaşmak için bir ön koşuldur.
Bu temel kavramlar şunlardır:
- saf fonksiyon
- değişmez Durum
- Yan etki
Tekrar ediyorum: Bu sadece bir paradigma, başka bir şey değil. Fonksiyonel yazılım yazmak için indirip kullandığımız fonksiyonel.js kütüphanesi yoktur. Bu sadece uygulama yazmayı düşünmenin bir yolu. En önemli temel kavramla başlayalım: saf işlev .
saf fonksiyon
Bir fonksiyon, iki basit kuralı takip ederse saf fonksiyon olarak kabul edilir:
- Aynı argümanları iletmek her zaman aynı değeri döndürür
- İşlev yürütme içinde gözlemlenebilir bir yan etkinin olmaması (harici bir durum değişikliği, G/Ç işleminin çağrılması vb.)
Yani saf bir işlev, bazı argümanları kabul eden (veya hiç argüman içermeyen) ve beklenen bir değer döndüren şeffaf bir fonksiyondur. Bu işlevi çağırmanın ağ oluşturma veya bazı genel kullanıcı durumlarını değiştirme gibi yan etkilere neden olmayacağından emin olabilirsiniz.
Üç basit örneğe bir göz atalım:
//Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }- İlk işlev saftır çünkü aynı argümanları iletirken her zaman aynı yanıtı döndürür.
- İkinci fonksiyon saf değildir çünkü deterministik değildir ve her çağrıldığında farklı cevaplar verir.
- Üçüncü işlev, bir yan etki kullandığı için saf değildir (
console.log).
Fonksiyonun saf olup olmadığını anlamak kolaydır. Saf bir işlev neden saf olmayandan daha iyidir? Çünkü düşünmek daha kolaydır. Bir kaynak kodu okuduğunuzu ve saf olduğunu bildiğiniz bir işlev çağrısı gördüğünüzü hayal edin. İşlev adı doğruysa, onu keşfetmenize gerek yoktur; hiçbir şeyi değiştirmediğini biliyorsun, beklediğini geri veriyor. Çok fazla iş mantığına sahip büyük bir kurumsal uygulamanız olduğunda hata ayıklamak için çok önemlidir, çünkü çok büyük bir zaman kazandırabilir.
Ayrıca, test etmek basittir. İçinde herhangi bir şey enjekte etmeniz veya bazı işlevlerle alay etmeniz gerekmez, sadece argümanları iletin ve sonucun bir eşleşme olup olmadığını test edin. Test ve mantık arasında güçlü bir bağlantı vardır: Bir bileşenin test edilmesi kolaysa, nasıl ve neden çalıştığını anlamak kolaydır.
Saf işlevler, not alma adı verilen çok kullanışlı ve performans dostu bir işlevle birlikte gelir. Aynı argümanları çağırmanın aynı değeri getireceğini biliyorsak, sonuçları basitçe önbelleğe alabilir ve tekrar çağırmakla zaman kaybetmeyiz. NgRx kesinlikle not almanın en üstünde yer alır; hızlı olmasının ana nedenlerinden biri budur.
Kendinize şunu sorabilirsiniz, “Ya yan etkiler? Nereye gidiyorlar?" Russ Olsen, GOTO konuşmasında, müşterilerimizin bize saf işlevler için değil, yan etkiler için ödeme yaptıkları konusunda şaka yapıyor. Bu doğru: Hesap Makinesi saf işlevi bir yere yazdırılmazsa kimsenin umurunda olmaz. Yan etkilerin işlevsel programlama evreninde yeri vardır. Bunu kısa süre sonra göreceğiz.
Şimdilik, karmaşık uygulama mimarilerinin bakımında bir sonraki adıma, sonraki temel konsepte geçelim: değişmez durum .
değişmez Durum
Değişmez bir durum için basit bir tanım vardır:
- Yalnızca bir durum oluşturabilir veya silebilirsiniz. Güncelleyemezsiniz.
Basit bir ifadeyle, bir Kullanıcı nesnesinin yaşını güncellemek için… :
let user = { username:"admin", age:28 }… şöyle yazmalısın:
// Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }Her değişiklik, özellikleri eskilerinden kopyalayan yeni bir nesnedir. Bu nedenle, zaten değişmez bir durumdayız.
Dize, Boolean ve Number tümü değişmez durumlardır: Var olan değerleri ekleyemez veya değiştiremezsiniz. Buna karşılık, Date değiştirilebilir bir nesnedir: Her zaman aynı tarih nesnesini değiştirirsiniz.
Değişmezlik uygulama genelinde geçerlidir: Yaşını değiştiren işlevin içine bir kullanıcı nesnesi iletirseniz, bir kullanıcı nesnesini değiştirmemeli, güncellenmiş bir yaşta yeni bir kullanıcı nesnesi oluşturmalı ve onu döndürmelidir:
function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);Neden buna zaman ve dikkat ayırmalıyız? Altını çizmeye değer birkaç fayda var.
Arka uç programlama dilleri için bir avantaj, paralel işlemeyi içerir. Durum değişikliği bir referansa bağlı değilse ve her güncelleme yeni bir nesneyse, işlemi parçalara ayırabilir ve aynı belleği paylaşmadan sayısız iş parçacığıyla aynı görev üzerinde çalışabilirsiniz. Hatta görevleri sunucular arasında paralel hale getirebilirsiniz.
Angular ve React gibi çerçeveler için paralel işleme, uygulama performansını iyileştirmenin daha faydalı yollarından biridir. Örneğin, Angular, bir bileşenin yeniden oluşturulması gerekip gerekmediğini ayırt etmek için Girdi bağlamaları aracılığıyla ilettiğiniz her nesnenin özelliklerini kontrol etmelidir. Ancak, varsayılan yerine ChangeDetectionStrategy.OnPush ayarlarsak, her bir özelliğe göre değil, referansa göre kontrol eder. Büyük bir uygulamada, bu kesinlikle zaman kazandırır. Durumumuzu değişmez bir şekilde güncellersek, bu performans artışını ücretsiz olarak elde ederiz.
Tüm programlama dillerinin ve çerçevelerinin paylaştığı değişmez bir durum için diğer fayda, saf işlevlerin faydalarına benzer: Düşünmek ve test etmek daha kolaydır. Bir değişiklik, eskisinden doğan yeni bir durum olduğunda, tam olarak ne üzerinde çalıştığınızı bilirsiniz ve durumun nasıl ve nerede değiştiğini tam olarak takip edebilirsiniz. Güncelleme geçmişini kaybetmezsiniz ve durum için değişiklikleri geri alabilir/yineleyebilirsiniz (React DevTools bir örnektir).
Ancak, tek bir durum güncellenirse, bu değişikliklerin geçmişini bilemezsiniz. Bir banka hesabının işlem geçmişi gibi değişmez bir durum düşünün. Pratik olarak olmazsa olmazlardandır.
Değişmezliği ve saflığı gözden geçirdiğimize göre, şimdi kalan temel kavramı ele alalım: yan etki .
Yan etki
Bir yan etkinin tanımını genelleştirebiliriz:
- Bilgisayar biliminde, bir işlemin, işlevin veya ifadenin, yerel ortamının dışındaki bazı durum değişkeni değerlerini değiştirirse yan etkisi olduğu söylenir. Başka bir deyişle, işlemin çağırıcısına bir değer (ana etki) döndürmenin yanı sıra gözlemlenebilir bir etkisi vardır.
Basitçe söylemek gerekirse, işlev kapsamı dışında bir durumu değiştiren her şey - tüm G/Ç işlemleri ve işleve doğrudan bağlı olmayan bazı işler - bir yan etki olarak kabul edilebilir. Ancak yan etkileri saf fonksiyonlar içinde kullanmaktan kaçınmalıyız çünkü yan etkiler fonksiyonel programlama felsefesiyle çelişir. Saf bir fonksiyon içinde bir G/Ç işlemi kullanırsanız, o zaman saf fonksiyon olmaktan çıkar.
Yine de bir yerde yan etkilere ihtiyacımız var, çünkü onlarsız bir uygulama anlamsız olurdu. Angular'da yalnızca saf işlevlerin yan etkilere karşı korunması gerekmez, bunları Bileşenler ve Yönergelerde de kullanmaktan kaçınmalıyız.
Bu tekniğin güzelliğini Angular framework içinde nasıl uygulayabileceğimizi inceleyelim.
Fonksiyonel Açısal Programlama
Angular hakkında anlaşılması gereken ilk şeylerden biri, daha kolay bakım ve test sağlamak için bileşenleri mümkün olduğunca sık olarak daha küçük bileşenlere ayırma ihtiyacıdır. İş mantığımızı bölmemiz gerektiğinden bu gereklidir. Ayrıca, Angular geliştiricileri, bileşenleri yalnızca oluşturma amacıyla bırakmaya ve tüm iş mantığını hizmetler içinde taşımaya teşvik edilir.
Bu kavramları genişletmek için Angular kullanıcıları kelime dağarcıklarına “Dumb-Smart Component” modelini eklediler. Bu model, servis çağrılarının küçük bileşenlerin içinde olmamasını gerektirir. İş mantığı hizmetlerde bulunduğundan, yine de bu hizmet yöntemlerini çağırmamız, yanıtlarını beklememiz ve ancak bundan sonra herhangi bir durum değişikliği yapmamız gerekir. Bu nedenle, bileşenlerin içinde bazı davranışsal mantık vardır.
Bunu önlemek için, iş ve davranış mantığını içeren bir Akıllı Bileşen (Kök Bileşen) oluşturabilir, durumları Girdi Özellikleri aracılığıyla iletebilir ve Çıktı parametrelerini dinleyerek eylemleri çağırabiliriz. Bu şekilde, küçük bileşenler gerçekten yalnızca oluşturma amaçlıdır. Tabii ki, Kök Bileşenimizin içinde bazı servis çağrıları olması gerekir ve bunları öylece kaldıramayız, ancak faydası işleme değil, yalnızca iş mantığıyla sınırlı olacaktır.
Bir Counter Component örneğine bakalım. Sayaç, değeri artıran veya azaltan iki düğme ve currentValue öğesini görüntüleyen bir displayField içeren bir bileşendir. Böylece dört bileşenle bitirdik:
- SayaçKonteyner
- Arttırma Düğmesi
- Azalt Düğmesi
- Mevcut değer
Tüm mantık CounterContainer içinde yaşar, bu nedenle üçü de yalnızca oluşturucudur. İşte üçünün kodu:
@Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }Bakın ne kadar basit ve saflar. Durumları veya yan etkileri yoktur, sadece girdi özelliklerine ve yayan olaylara bağlıdırlar. Onları test etmenin ne kadar kolay olduğunu hayal edin. Gerçekte oldukları gibi onlara saf bileşenler diyebiliriz. Yalnızca girdi parametrelerine bağlıdırlar, yan etkileri yoktur ve aynı parametreleri ileterek her zaman aynı değeri (şablon dizesi) döndürürler.

Böylece fonksiyonel programlamadaki saf fonksiyonlar, Angular'daki saf bileşenlere aktarılır. Ama tüm mantık nereye gidiyor? Mantık hala orada ama biraz farklı bir yerde, yani CounterComponent .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } } Gördüğünüz gibi, davranış mantığı CounterContainer yaşıyor ancak işleme kısmı eksik (şablonun içindeki bileşenleri bildiriyor), çünkü işleme kısmı saf bileşenler için.
İstediğimiz kadar servis enjekte edebiliriz çünkü tüm veri manipülasyonlarını ve durum değişikliklerini burada hallediyoruz. Bahsetmeye değer bir şey, eğer derin iç içe bir bileşenimiz varsa, yalnızca bir kök düzeyinde bileşen oluşturmamamız gerektiğidir. Daha küçük akıllı bileşenlere bölüp aynı kalıbı kullanabiliriz. Sonuç olarak, her bileşenin karmaşıklığına ve iç içe geçmiş düzeyine bağlıdır.
Bu kalıptan, üzerinde sadece bir katman olan NgRx kitaplığının kendisine kolayca atlayabiliriz.
NgRx Kitaplığı
Herhangi bir web uygulamasını üç ana bölüme ayırabiliriz:
- İş mantığı
- Uygulama Durumu
- İşleme Mantığı
İş Mantığı , ağ oluşturma, giriş, çıkış, API vb. gibi uygulamada meydana gelen tüm davranışlardır.
Uygulama Durumu , uygulamanın durumudur. Halihazırda yetkili kullanıcı olarak global olabileceği gibi, mevcut Sayaç Bileşeni değeri olarak da yerel olabilir.
İşleme Mantığı , DOM kullanarak veri görüntüleme, öğe oluşturma veya kaldırma vb. gibi işlemeyi kapsar.
Dumb-Smart modelini kullanarak Rendering Logic'i İş Mantığı ve Uygulama Durumundan ayırdık, ancak her ikisi de kavramsal olarak birbirinden farklı olduğu için onları da bölebiliriz. Uygulama Durumu, uygulamanızın o anki anlık görüntüsü gibidir. İş Mantığı, uygulamanızda her zaman mevcut olan statik bir işlevsellik gibidir. Bunları bölmenin en önemli nedeni, Business Logic'in çoğunlukla uygulama kodunda mümkün olduğunca kaçınmak istediğimiz bir yan etki olmasıdır. Bu, işlevsel paradigmasıyla NgRx kitaplığının parladığı zamandır.
NgRx ile tüm bu parçaları birbirinden ayırırsınız. Üç ana bölüm vardır:
- redüktörler
- Hareketler
- seçiciler
İşlevsel programlamayla birleştiğinde, her üçü de bize her boyuttaki uygulamayı işlemek için güçlü bir araç sunmak için birleşir. Her birini inceleyelim.
redüktörler
Bir redüktör, basit bir imzaya sahip saf bir işlevdir. Parametre olarak eski bir durumu alır ve eskisinden veya yenisinden türetilen yeni bir durum döndürür. Durumun kendisi, uygulamanızın yaşam döngüsüyle birlikte yaşayan tek bir nesnedir. Bir HTML etiketi, tek bir kök nesne gibi.
Bir durum nesnesini doğrudan değiştiremezsiniz, onu redüktörlerle değiştirmeniz gerekir. Bunun bir takım faydaları vardır:
- Durum değiştirme mantığı tek bir yerde yaşar ve durumun nerede ve nasıl değiştiğini bilirsiniz.
- Redüktör işlevleri, test edilmesi ve yönetilmesi kolay olan saf işlevlerdir.
- Redüktörler salt işlevler olduğundan, bunlar belleğe alınabilir, bu da onları önbelleğe almayı ve fazladan hesaplamadan kaçınmayı mümkün kılar.
- Durum değişiklikleri değişmezdir. Aynı örneği asla güncellemezsiniz. Bunun yerine, her zaman yenisini iade edersiniz. Bu, bir "zaman yolculuğu" hata ayıklama deneyimi sağlar.
Bu, bir redüktörün önemsiz bir örneğidir:
function usernameReducer(oldState, username) { return {...oldState, username} }Çok basit bir kukla redüktör olmasına rağmen, tüm uzun ve karmaşık redüktörlerin iskeletidir. Hepsi aynı faydaları paylaşıyor. Uygulamamızda yüzlerce redüktör olabilir ve istediğimiz kadar yapabiliriz.
Sayaç Bileşenimiz için durumumuz ve redüktörlerimiz şöyle görünebilir:
interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }Durumu bileşenden kaldırdık. Şimdi durumumuzu güncellemek ve uygun redüktörü çağırmak için bir yola ihtiyacımız var. İşte o zaman eylemler devreye girer.
Hareketler
Eylem, NgRx'i bir redüktör çağırması ve durumu güncellemesi için bilgilendirmenin bir yoludur. Bu olmadan, NgRx kullanmak anlamsız olurdu. Eylem, akım düşürücüye eklediğimiz basit bir nesnedir. Çağırdıktan sonra uygun redüktör çağrılacak, bu nedenle örneğimizde aşağıdaki eylemlere sahip olabiliriz:
enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);Eylemlerimiz redüktörlere bağlıdır. Artık Konteyner Bileşenimizi daha fazla değiştirebilir ve gerektiğinde uygun eylemleri çağırabiliriz:
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Not: Durumu kaldırdık ve kısa süre sonra tekrar ekleyeceğiz .
Artık CounterContainer herhangi bir durum değiştirme mantığına sahip değil. Sadece ne göndereceğini biliyor. Şimdi bu verileri görünümde göstermenin bir yoluna ihtiyacımız var. Seçicilerin faydası budur.
seçiciler
Bir seçici de çok basit bir saf işlevdir, ancak bir indirgeyiciden farklı olarak durumu güncellemez. Adından da anlaşılacağı gibi, seçici yalnızca onu seçer. Örneğimizde, üç basit seçicimiz olabilir:
function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; } Bu seçicileri kullanarak, akıllı CounterContainer bileşenimiz içinde bir durumun her bir dilimini seçebiliriz.
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Bu seçimler varsayılan olarak eşzamansızdır (genel olarak Gözlenebilirler gibi). En azından model açısından bunun hiçbir önemi yoktur. Aynısı, durumumuzdan bir şey seçeceğimiz için senkronize olan için de geçerli olacaktır.
Şu ana kadar neler başardığımızı görmek için bir adım geriye gidelim ve büyük resme bakalım. Neredeyse birbirinden kopuk üç ana bölümden oluşan bir Sayaç Uygulamamız var. Uygulama durumunun kendisini nasıl yönettiğini veya işleme katmanının durumu nasıl işlediğini kimse bilmiyor.
Ayrılan parçalar birbirine bağlanmak için köprüyü (Eylemler, Seçiciler) kullanır. Tüm Devlet Uygulaması kodunu alıp başka bir projeye taşıyabileceğimiz, örneğin mobil sürüm için olduğu gibi, ayrışmışlardır. Uygulamamız gereken tek şey render olurdu. Ama ya test?
Benim düşünceme göre, test etmek NgRx'in en iyi kısmıdır. Bu örnek projeyi test etmek, tic-tac-toe oynamaya benzer. Yalnızca saf işlevler ve saf bileşenler vardır, bu nedenle bunları test etmek çok kolaydır. Şimdi bu projenin yüzlerce bileşenle büyüdüğünü hayal edin. Aynı kalıbı takip edersek, daha fazla parçayı bir araya getirirdik. Dağınık, okunamaz bir kaynak kodu bloğu haline gelmezdi.
Neredeyse tamamız. Kapsanması gereken tek bir önemli şey kaldı: yan etkiler. Şimdiye kadar yan etkilerden birçok kez bahsettim ama onları nerede saklayacağımı açıklamaktan vazgeçtim.
Bunun nedeni, yan etkilerin pastanın üzerindeki krema olması ve bu kalıbı oluşturarak bunları uygulama kodundan çıkarmak çok kolay.
Yan etkiler
Diyelim ki Sayaç Uygulamamızın içinde bir zamanlayıcı var ve her üç saniyede bir değeri otomatik olarak bir artırıyor. Bu, bir yerde yaşamak zorunda olan basit bir yan etkidir. Tanımı gereği bir Ajax isteğiyle aynı yan etkidir.
Yan etkileri düşünürsek, çoğunun var olmak için iki ana nedeni vardır:
- Devlet ortamının dışında herhangi bir şey yapmak
- Uygulama durumu güncelleniyor
Örneğin, bazı durumları LocalStorage içinde depolamak ilk seçenektir, durumu Ajax yanıtından güncellemek ise ikinci seçenektir. Ancak ikisi de aynı imzayı paylaşıyor: Her yan etkinin bir başlangıç noktası olması gerekiyor. Eylemi başlatmasını istemek için en az bir kez çağrılması gerekir.
Daha önce ana hatlarıyla belirttiğimiz gibi, NgRx birisine komut vermek için güzel bir araca sahiptir. Bu bir eylem. Bir eylem göndererek herhangi bir yan etki diyebiliriz. Sözde kod şöyle görünebilir:
function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Oldukça önemsiz. Daha önce de belirttiğim gibi, yan etkiler bir şeyi günceller veya güncellemez. Bir yan etki hiçbir şeyi güncellemiyorsa yapacak bir şey yok; sadece bırakıyoruz. Ancak bir durumu güncellemek istersek bunu nasıl yaparız? Bir bileşenin bir durumu güncellemeye çalışmasıyla aynı şekilde: başka bir eylemi çağırmak. Bu yüzden, durumu güncelleyen yan etki içinde bir eylem diyoruz:
function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Artık tamamen işlevsel bir uygulamamız var.
NgRx Deneyimimizi Özetlemek
NgRx yolculuğumuzu bitirmeden önce bahsetmek istediğim bazı önemli konular var:
- Gösterilen kod, makale için icat ettiğim basit sözde koddur; sadece tanıtım amaçlıdır. NgRx, gerçek kaynakların yaşadığı yerdir.
- İşlevsel programlamayı NgRx kitaplığına bağlama konusundaki teorimi kanıtlayan resmi bir kılavuz yok. Bu sadece benim düşüncem, çok yetenekli kişiler tarafından oluşturulmuş onlarca makale ve kaynak kod örneğini okuduktan sonra oluştu.
- NgRx'i kullandıktan sonra, bunun bu basit örnekten çok daha karmaşık olduğunu kesinlikle anlayacaksınız. Amacım, gerçekte olduğundan daha basit görünmesini sağlamak değil, biraz karmaşık olmasına ve hedefe daha uzun bir yol ile sonuçlanmasına rağmen, ek çabaya değer olduğunu size göstermekti.
- NgRx'in en kötü kullanımı, uygulamanın boyutu veya karmaşıklığı ne olursa olsun onu her yerde kullanmaktır. NgRx'i kullanmamanız gereken bazı durumlar vardır; örneğin formlarda. NgRx içinde formları uygulamak neredeyse imkansız. Formlar, DOM'nin kendisine yapıştırılır; ayrı yaşayamazlar. Onları ayırmaya çalışırsanız, kendinizi yalnızca NgRx'ten değil, genel olarak web teknolojisinden nefret ederken bulacaksınız.
- Bazen aynı ortak kodu kullanmak, küçük bir örnek için bile, gelecekte bize fayda sağlayabilecek olsa bile bir kabusa dönüşebilir. Durum buysa, NgRx ekosisteminin (ComponentStore) bir parçası olan başka bir harika kitaplıkla entegre edin.
