Programmazione reattiva e proattiva con RxJS

Pubblicato il

(Ben Copeland) (25 marzo , 2020)

Se utilizziamo le librerie ReactiveX come RxJS, sappiamo che stiamo implementando tecnicamente una soluzione di programmazione reattiva: utilizziamo il concetto di flussi osservabili di RxJS e il nostro codice reagisce ai nuovi eventi nel flusso. Tuttavia, anche se utilizziamo una libreria reattiva, è possibile progettare il nostro software secondo un paradigma più proattivo piuttosto che reattivo. Tuttavia, siamo molto più serviti da queste biblioteche se abbiamo uno stato danimo reattivo.

Prendiamo un momento per identificare la distinzione tra un approccio proattivo e reattivo. Con proattivo mi riferisco a un approccio in cui leditore (o il soggetto) è a conoscenza degli abbonati esterni e dei loro stati desiderati, e invia gli aggiornamenti ai sottoscrittori noti. Laccoppiamento con il consumatore avviene dal lato editore. Con reattivo mi riferisco a un approccio in cui labbonato riceve un aggiornamento e lo gestisce da solo.

Considera un recente scenario che abbiamo riscontrato su IQueue for Clinics. Abbiamo unapp Angular che utilizza pesantemente RxJS e volevamo pubblicare ogni modifica che emerge nellhook del ciclo di vita ngOnChanges di un componente a un flusso che gli abbonati potevano utilizzare.

Ecco un esempio semplificato, in cui abbiamo un componente con tre proprietà @Input e abbiamo alcuni componenti che vogliono tenere traccia di un oggetto che ha il valore più recente di ciascuna delle le tre proprietà.

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 });
}
}
}

Nellesempio sopra, creiamo un oggetto con ciascuna delle tre possibili proprietà per la memorizzazione nella cache, quindi su ngOnChanges, uniamo gli aggiornamenti da una qualsiasi delle tre proprietà in quelloggetto e aggiorniamo il flusso con il valore più recente delloggetto combinato. Questo è un approccio proattivo: il soggetto conosce lintento dellosservatore. Se viene aggiunto un altro osservatore che ha un intento diverso, o se cambia lintenzione dellosservatore corrente, limplementazione del soggetto deve essere cambiata.

Lapproccio sopra è essenzialmente quello con cui abbiamo iniziato, quindi per rifattorizzarlo per renderlo più reattivo. Affinché ciò accadesse, dovevamo semplicemente pubblicare il valore più recente senza che i consumatori ne fossero a conoscenza. Quella che segue è una versione aggiornata del componente precedente che pubblica solo gli aggiornamenti correnti.

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

changes$: Subject = new Subject();

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

E poi gestiamo laggregazione in un singolo oggetto nella classe che consuma losservabile.

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
);

Ora, losservabile pubblica solo nuovi eventi e labbonato sta sfruttando uno dei molti potenti tubi di RxJS chiamato scansione per mantenere uno stato accumulato degli ultimi valori di tutte le proprietà possibili che stanno arrivando attraverso il flusso. Pensa alla scansione come a una funzione di riduzione che emette lultimo valore accumulato su ogni evento. Quindi, dopo il refactoring, abbiamo un oggetto generico che invia ogni possibile evento ngOnChanges. Se vengono aggiunti più @Inputs, verranno inviati naturalmente anche quelli. Tutti gli abbonati che stanno ascoltando il flusso possono fare ciò che ritengono opportuno con il flusso di eventi, sia che si tratti di accumulare un oggetto di tutti gli eventi o di ascoltare solo le modifiche su una singola proprietà.

Mentre RxJS dà noi gli strumenti per fare programmazione reattiva, è ancora possibile progettare codice che segua un paradigma più proattivo. Per essere più reattivi, possiamo mantenere la nostra pubblicazione il più agnostica possibile e consentire agli abbonamenti di implementare una logica più complicata.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *