Reattività avanzata

Approfondimento 11.5

Percentuale Tradotta

In questo capitolo imparerai:

  • Impara a creare sorgenti di dati reattive in Meteor.
  • Crea un semplice esempio di sorgente di dati reattiva.
  • Vedi come Deps si compara ad AngularJS.
  • È raro aver bisogno di scrivere da soli il codice per risolvere le dipendenze, ma è certamente utile per capire come avviene il flusso di risoluzione delle dipendenze.

    Immaginate di voler monitorare il numero di amici di Facebook dell'utente corrente che hanno messo “Mi piace” su ogni post su Microscope. Supponiamo che abbiamo già definito i dettagli su come autenticare l'utente con Facebook, le opportune chiamate alle API, e estrarre i dati rilevanti. Ora abbiamo una funzione asincrona lato client che restituisce il numero di mi piace, getFacebookLikeCount(utente, url, callback).

    La cosa importante da ricordare su questa funzione è che è non-reattiva e non-realtime. Farà una richiesta HTTP a Facebook per recuperare dei dati, e li renderà disponibili per l'applicazione in una richiamata asincrona, ma la funzione non si ri-eseguirà da sola quando il contatore cambia su Facebook, e la nostra interfaccia non cambierà quando i dati sottostanti lo faranno.

    Per risolvere questo problema, possiamo iniziare utilizzando setInterval per chiamare la nostra funzione ogni pochi secondi:

    currentLikeCount = 0;
    Meteor.setInterval(function() {
      var postId;
      if (Meteor.user() && postId = Session.get('currentPostId')) {
        getFacebookLikeCount(Meteor.user(), Posts.find(postId),
          function(err, count) {
            if (!err)
              currentLikeCount = count;
          });
      }
    }, 5 * 1000);
    

    Ogni volta che controlliamo la variabile currentLikeCount, possiamo aspettarci di ottenere il numero corretto con un margine di errore di cinque secondi. Possiamo ora utilizzare questa variabile in un helper in questo modo:

    Template.postItem.likeCount = function() {
      return currentLikeCount;
    }
    

    Tuttavia, nulla ancora dice al nostro template di ri-disegnare quando currentLikeCount cambia. Anche se la variabile è ora pseudo-realtime, nel senso che cambia da sola, non è reattiva così ancora non può comunicare correttamente con il resto dell'ecosistema Meteor.

    Tracciare la reattività: Computation

    La reattività di Meteor è mediata dalle dipendenze, strutture di dati che tracciano una serie di calcoli.

    Come abbiamo visto nel precedente approfondimento reattività, una computation è una sezione di codice che utilizza i dati reattivi. Nel nostro caso, c'è una computation che è stata creata in modo implicito per il modello PostItem. Ogni helper sul gestore del template sta lavorando all'interno di tale computation.

    Si può pensare alla computation, come la sezione di codice che “si interessa” dei dati reattivi. Quando i dati cambiano, sarà questa computation che sarà informata (via invalidate ()), ed è la computation che decide se c'é bisogno di fare qualcosa.

    Trasformare una variabile in una funzione reattiva

    Per trasformare la nostra variabile currentLikeCount variabile in una fonte di dati reattiva, abbiamo bisogno di tenere traccia di tutte le computation che la utilizzano in una dipendenza. Ciò richiede di trasformarla da variabile a funzione (che restituirà un valore):

    var _currentLikeCount = 0;
    var _currentLikeCountListeners = new Deps.Dependency();
    
    currentLikeCount = function() {
      _currentLikeCountListeners.depend();
      return _currentLikeCount;
    }
    
    Meteor.setInterval(function() {
      var postId;
      if (Meteor.user() && postId = Session.get('currentPostId')) {
        getFacebookLikeCount(Meteor.user(), Posts.find(postId),
          function(err, count) {
            if (!err && count !== _currentLikeCount) {
              _currentLikeCount = count;
              _currentLikeCountListeners.changed();
            }
          });
      }
    }, 5 * 1000);
    

    Quello che abbiamo fatto è impostare una dipendenza _currentLikeCountListeners, che tiene traccia di tutte le computation nelle quali currentLikeCount() è stato usato. Quando il valore _currentLikeCount cambia, chiamiamo la funzione changed() su tale dipendenza, che invalida tutte le computation tracciate.

    Queste computation possono quindi andare avanti e gestire il cambiamento, caso per caso.

    Comparare Deps ad Angular

    Angular è una libreria solo client-side di rendering reattivo, ed è sviluppata dai bravi ragazzi di Google. È indicativo confrontare l'approccio di Meteor per il tracciamento delle dipendenze con quello di Angular, visto che gli approcci sono molto diversi.

    Abbiamo visto che il modello di Meteor utilizza i blocchi di codice chiamati computation. Queste computation sono monitorate da speciali sorgenti di dati “reattive” (funzioni) che si prendono cura loro di invalidare al momento opportuno. Così la sorgente di dati esplicitamente informa tutte le sue dipendenze quando hanno bisogno di chiamare invalidate(). Si noti che anche se questo accade generalmente quando i dati sono cambiati, la sorgente dei dati potrebbe potenzialmente anche decidere di attivare invalidazione per altri motivi.

    Inoltre, anche se la computation di solito solo riesegue quando invalidata, è possibile impostarla fino a comportarsi nel modo desiderato. Tutto questo ci dà un elevato livello di controllo sulla reattività.

    In Angular, la reattività è mediata dall'oggetto scope. Uno scope può essere pensato come semplice oggetto JavaScript con un paio di metodi speciali.

    Quando si vuole dipendere reattivamente su un valore in uno scope, si chiama scope.$watch, fornendo l'espressione alla quale si è interessati (vale a dire quali parti dello scope vi interessano) e una funzione listener che verrà eseguita ogni volta tale espressione cambia. Così si dichiara esplicitamente esattamente quello che si vuole fare ogni volta che il valore dell'espressione cambia.

    Tornando al nostro esempio Facebook, scriveremo:

    $rootScope.$watch('currentLikeCount', function(likeCount) {
      console.log('Current like count is ' + likeCount);
    });
    

    Naturalmente, proprio come raramente si imposta una computation in Meteor, neanche $watch viene chiamato spesso esplicitamente in Angular come direttiva ng-model e {{expressions}} imposta automaticamente il tracciamento che poi si prende cura del re-rendering ad ogni cambiamento.

    Quando un certo valore reattivo è cambiato, scope.$apply() deve poi essere chiamato. Questo rivaluta ogni osservatore del campo dello scope, ma chiama solo la funzione listener di watcher il cui valore di espressione è cambiato.

    Quindi scope.$apply() è simile a dependency.changed(), tranne che agisce a livello del campo dello scope, piuttosto che dare il comando di dire che proprio gli ascoltatori dovrebbero essere rivalutato. Detto questo, questa leggera mancanza di controllo dà ad Angular la capacità di essere molto intelligente ed efficiente nel modo in cui determina con precisione quali listener devono essere rivalutati.

    Con Angular, il codice della nostra funzione getFacebookLikeCount() codice sarebbe assomigliato a qualcosa di simile:

    Meteor.setInterval(function() {
      getFacebookLikeCount(Meteor.user(), Posts.find(postId),
        function(err, count) {
          if (!err) {
            $rootScope.currentLikeCount = count;
            $rootScope.$apply();
          }
        });
    }, 5 * 1000);
    

    Certo, Meteor si occupa della maggior parte del lavoro pesante per noi e ci permette di beneficiare della reattività senza molto lavoro da parte nostra. Ma si spera che impararando questi pattern possa tornare utile se ci sarà mai bisogno di dover andare oltre.