Réactivité Avancée

Aparté 11.5

Traduction complétée à

Au programme de ce chapitre :

  • Apprendre comment créer des sources de données réactives dans Meteor.
  • Créer un simple exemple de source de données réactives.
  • Voir comment Tracker se compare à AngularJS.
  • Il est rare d'avoir besoin d'écrire du code de traçage vous-même, mais il est assurément utile de le comprendre pour comprendre la façon dont le processus de résolution de dépendance fonctionne.

    Imaginez que nous voulions tracer combien d'amis Facebook de l'utilisateur courant ont “aimé” chaque article sur Microscope. Partons du principe que nous avons déjà travaillé sur les détails de l'authentification de l'utilisateur avec Facebook, fait les appels appropriés vers l'API, et fait l'analyse des données pertinentes. Nous avons maintenant une fonction asynchrone côté client qui retourne le nombre de “j'aime”, getFacebookLikeCount(user, url, callback).

    La chose importante à retenir au sujet d'une telle fonction est qu'elle est vraiment non réactive et non temps réel. Elle fera une requête HTTP vers Facebook, récupérera des données, et les rendra disponible à l'application via un rappel asynchrone, mais la fonction ne s'exécutera pas une nouvelle fois par elle-même quand le compte changera chez Facebook, et notre UI ne changera pas quand les données sous-jacentes le feront.

    Pour corriger cela, nous pouvons démarrer en utilisant setInterval pour appeler notre fonction toutes les quelques secondes :

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

    Quel que soit le moment où nous vérifions cette variable currentLikeCount, nous pouvons nous attendre à avoir un nombre correct avec une marge d'erreur de cinq secondes. Nous pouvons utiliser cette variable dans un helper comme suit :

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

    Cependant, rien ne dit à notre template de se recharger quand currentLikeCount change. Bien que la variable soit maintenant pseudo temps réel du fait qu'elle change par elle-même, elle n'est pas réactive donc elle ne peut pas tout à fait communiquer proprement avec le reste de l'écosystème Meteor.

    Réactivité de la surveillance : Computations (Calculs)

    La réactivité de Meteor est contrôlée par des dépendances, des structures de données qui surveillent un ensemble de calculs.

    Comme nous l'avons vu dans un précédent aparté sur la réactivité, un calcul est une section de code qui utilise des données réactives. Dans notre cas, un calcul a été implicitement créé pour le template postItem, chaque helper de ce gestionnaire de template a sa propre partie calculs.

    Vous pouvez penser au calcul comme à la section de code qui “s'occupe” de la source de données réactives. Quand la donnée change, ce sera ce calcul qui en sera informé (via invalidate()), et c'est le calcul qui décide si quelque chose doit être fait.

    Transformer une Variable en une Fonction Réactive

    Pour transformer notre variable currentLikeCount en source de données réactives, nous avons besoin de surveiller tous les calculs qui l'utilisent dans une dépendance. Cela requiert de la transformer de variable à fonction (qui retourne une valeur) :

    var _currentLikeCount = 0;
    var _currentLikeCountListeners = new Tracker.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);
    

    Nous avons mis en place une dépendance _currentLikeCountListeners, qui surveille tous les calculs dans lesquels currentLikeCount() a été utilisé. Quand la valeur de _currentLikeCount change, nous appelons la fonction changed() sur cette dépendance, qui invalide tous les calculs surveillés.

    Ces calculs peuvent ensuite continuer et traiter le changement au cas par cas.

    Si cela vous semble être une approche un peu rébarbative pour une simple source de données réactive, vous avez raison, et Meteor fournit des outils intégrés pour rendre ça un peu plus simple (tout comme vous n'avez pas besoin d'utiliser des calculs directement, vous utilisez d'habitude des autoruns). Il y a un paquet plate-forme appelé reactive-var qui fait exactement ce que notre currentLikeCount() fonction fait. Si nous l'ajoutons :

    meteor add reactive-var
    

    Nous pouvons l'utiliser pour simplifier notre code un petit peu :

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

    Maintenant, pour l'utiliser, nous appellerons currentLikeCount.get() dans notre helper et cela fonctionnera comme avant. Il y a aussi un autre paquet plate-forme reactive-dict, qui fournit un entrepôt réactif clé-valeur (presque exactement comme Session), qui peut être aussi utile.

    Comparer Tracker à Angular

    Angular est une bibliothèque de rendu réactif côté client seulement, développé par les bonnes gens de Google. Il est intéressant de comparer l'approche de surveillance de dépendance de Meteor à celle d'Angular, car celles-ci sont assez différentes.

    Nous avons vu que le modèle de Meteor utilise des blocs de code appelés computations (calculs). Ces calculs sont traqués par des sources de données “réactives” (fonctions) qui prennent soin de les invalider quand c'est approprié. Donc les sources de données informent explicitement toutes ses dépendances quand elles ont besoin d'appeler invalidate(). Notez que bien que cela se passe généralement quand les données ont changé, la source de données pourrait potentiellement décider de déclencher une invalidation pour d'autres raisons.

    De plus, bien que les calculs se re-exécutent habituellement quand ils sont invalidés, vous pouvez les configurer pour se comporter comme vous le voulez. Tout ceci nous donne un haut niveau de contrôle sur la réactivité.

    Dans Angular, la réactivité est contrôlée par l'objet scope. Un scope peut être imaginé comme un objet JavaScript simple avec plusieurs méthodes spéciales.

    Quand vous voulez dépendre activement d'une valeur dans un scope, vous appelez scope.$watch, en fournissant l'expression qui vous intéresse (c'est-à-dire la partie du scope qui vous intéresse) et une fonction listener qui s'exécutera à chaque fois que la valeur de l'expression change.

    De retour sur notre exemple Facebook, nous écririons :

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

    Bien sûr, tout comme vous ne mettez que rarement en place des calculs dans Meteor, vous n'appelez pas souvent $watch explicitement dans Angular puisque les directives ng-model et les {{expressions}} configurent automatiquement des watches qui ensuite s'occupent de réactualiser la page.

    Quand ce type de valeur réactive a changé, scope.$apply() doit être appelé. Ceci réévalue chaque watcher du scope, mais appelle seulement la fonction listener des veilleurs pour qui la valeur de l'expression a changé.

    scope.$apply() est similaire à dependency.changed(), excepté qu'il agit au niveau du scope, plutôt que vous donner le contrôle de dire précisément quels listeners doivent être réévalués. Ceci dit, ce léger manque de contrôle donne à Angular l'habilité d'être vraiment intelligent et efficace dans la manière dont il détermine précisément quels listeners doivent être réévalués.

    Avec Angular, notre code de fonction getFacebookLikeCount() devrait ressembler à ça :

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

    Certes, Meteor s'occupe du gros du travail pour nous et nous laisse bénéficier de la réactivité sans trop de travail de notre part. Mais heureusement, apprendre ces pratiques s’avérera utile si vous avez besoin de pousser les choses un peu plus loin.