-
Notifications
You must be signed in to change notification settings - Fork 32
/
11s-advanced-reactivity.md.erb
155 lines (115 loc) · 9 KB
/
11s-advanced-reactivity.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
---
title: Réactivité Avancée
slug: advanced-reactivity
date: 0011/01/02
number: 11.5
points: 10
sidebar: true
photoUrl: http://www.flickr.com/photos/ikewinski/8676146109/
photoAuthor: Mike Lewinski
contents: 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.
paragraphs: 29
---
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 :
~~~js
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 :
~~~js
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) :
~~~js
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);
~~~
<%= highlight "1~7,14~17" %>
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 :
~~~bash
meteor add reactive-var
~~~
Nous pouvons l'utiliser pour simplifier notre code un petit peu :
~~~js
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);
~~~
<%= highlight "1,9" %>
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](http://angularjs.org/) 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 :
~~~js
$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 :
~~~js
Meteor.setInterval(function() {
getFacebookLikeCount(Meteor.user(), Posts.find(postId),
function(err, count) {
if (!err) {
$rootScope.currentLikeCount = count;
$rootScope.$apply();
}
});
}, 5 * 1000);
~~~
<%= highlight "5~6" %>
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.