Programowanie reaktywne a programowanie proaktywne z RxJS

(Ben Copeland) (25 marca , 2020)

Jeśli korzystamy z bibliotek ReactiveX, takich jak RxJS, wiemy, że technicznie wdrażamy reaktywne rozwiązanie programistyczne – używamy koncepcji obserwowalnych strumieni RxJS, a nasz kod reaguje na nowe zdarzenia w strumieniu. Jednak nawet jeśli korzystamy z biblioteki reaktywnej, możliwe jest zaprojektowanie naszego oprogramowania zgodnie z bardziej proaktywnym paradygmatem niż reaktywnym. Jednak te biblioteki znacznie lepiej nam służą, jeśli mamy reaktywne nastawienie.

Poświęćmy chwilę, aby zidentyfikować różnicę między podejściem proaktywnym a reaktywnym. Przez proaktywne mam na myśli podejście, w którym wydawca (lub podmiot) jest świadomy zewnętrznych subskrybentów i ich pożądanych stanów oraz przesyła aktualizacje do znanych subskrybentów. Sprzężenie z konsumentem następuje po stronie wydawcy. Przez reaktywne mam na myśli podejście, w którym subskrybent otrzymuje aktualizację i obsługuje ją samodzielnie.

Rozważmy niedawny scenariusz, który napotkaliśmy w IQueue for Clinics. Mamy aplikację Angular, która w dużym stopniu korzysta z RxJS i chcieliśmy publikować każdą zmianę, która pojawia się w ngOnChanges haku cyklu życia komponentu, do strumienia, z którego mogliby korzystać subskrybenci.

Oto uproszczony przykład, w którym mamy komponent z trzema właściwościami @Input i mamy kilka komponentów, które chcą śledzić obiekt, który ma najnowszą wartość każdej z trzy właściwości.

interface IChanges { a: string; b: string; c: string; }

@Component({...})
class SomeComponent {
@Input() a;
@Input() b;
@Input() c;

changes$: BehaviorSubject = new BehaviorSubject({
a: null,
b: null,
c: null,
});

allLatestChanges: IChanges;

ngOnChanges(changes: SimpleChanges) {
if (changes.a) {
changes$.next(Object.assign(allLatestChanges, { a: changes.a.currentValue });
}
if (changes.b) {
changes$.next(Object.assign(allLatestChanges, { b: changes.b.currentValue });
}
if (changes.c) {
changes$.next(Object.assign(allLatestChanges, { c: changes.c.currentValue });
}
}
}

W powyższym przykładzie tworzymy obiekt z każdą z trzech możliwych właściwości do buforowania, a następnie ngOnChanges, scalamy aktualizacje z dowolnej z trzech właściwości w ten obiekt i aktualizujemy strumień o najnowszą wartość połączonego obiektu. Jest to podejście proaktywne – osoba badana zna intencje obserwatora. Jeśli dodany zostanie kolejny obserwator, który ma inne intencje, lub jeśli zamiar obecnego obserwatora ulegnie zmianie, implementacja tematu musi zostać zmieniona.

Powyższe podejście jest zasadniczo tym, od czego zaczęliśmy, a następnie poszliśmy na refaktoryzacji, aby była bardziej reaktywna. Aby tak się stało, musieliśmy, aby temat po prostu opublikował najnowsze wartości bez wiedzy konsumentów. Poniżej znajduje się zaktualizowana wersja wcześniejszego komponentu, który publikuje tylko bieżące aktualizacje.

@Component({...})
class SomeComponent {
@Input() a;
@Input() b;
@Input() c;

changes$: Subject = new Subject();

ngOnChanges(changes: SimpleChanges) {
changes$.next(changes);
}
}

Następnie obsługujemy agregację do pojedynczego obiektu w klasie, który zużywa obserwowalne.

defaultAcc = {
a: null,
b: null,
c: null,
}

allChanges$ = changes$
.pipe(
scan((acc, curr) =>
Object.assign(acc, Object.fromEntries(
Object.entries(curr).map((k,v) => { [k]: v.currentValue })
)
)), defaultAcc
);

Teraz to, co obserwowalne publikuje tylko nowe zdarzenia, a subskrybent wykorzystuje jeden z wielu potężnych potoków RxJS zwanych skanowaniem, aby zachować skumulowany stan najnowszych wartości wszystkich możliwych właściwości, które przechodzą przez strumień. Pomyśl o skanowaniu jako funkcji redukcji, która generuje najnowszą skumulowaną wartość dla każdego zdarzenia. Tak więc po refaktoryzacji mamy temat ogólnego przeznaczenia, który wywołuje każde możliwe zdarzenie ngOnChanges. Jeśli dodanych zostanie więcej @Inputs, to w naturalny sposób też je wyśle. Każdy subskrybent, który słucha strumienia, może zrobić ze strumieniem zdarzenia to, co uzna za stosowne – niezależnie od tego, czy jest to gromadzenie obiektu wszystkich zdarzeń, czy tylko nasłuchiwanie zmian w jednej właściwości.

Podczas gdy RxJS podaje jeśli dysponujemy narzędziami do programowania reaktywnego, nadal można zaprojektować kod zgodny z bardziej proaktywnym paradygmatem. Aby być bardziej reaktywnym, możemy utrzymywać naszą publikację tak agnostyczną, jak to tylko możliwe, i zezwolić lub subskrypcjom na implementację bardziej skomplikowanej logiki.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *