-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy path11s-advanced-reactivity.md.erb
124 lines (91 loc) · 7.58 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
---
title: Reattività avanzata
slug: advanced-reactivity
date: 0011/01/02
number: 11.5
sidebar: true
paragraphs: 29
contents: 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:
~~~js
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:
~~~js
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):
~~~js
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);
~~~
<%= highlight "1~7,14~17" %>
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] (http://angularjs.org) è 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:
~~~js
$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:
~~~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" %>
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.