雖然需要你自己寫代碼來跟蹤依賴變量的情況十分罕見,了解依賴變量的工作流程還是十分必要的。
設想我們現(xiàn)在需要跟蹤一下 Microscope上,當前用戶的 Facebook 朋友在 “l(fā)ike” 某一篇帖子的數(shù)量。 讓我們假設我們已經(jīng)解決了 Facebook 用戶認證的問題,運用了正確的 API 調用,而且也解析了相關數(shù)據(jù)。 我們現(xiàn)在有一個異步的客戶端函數(shù)返回 like 的數(shù)量,getFacebookLikeCount(user, url, callback)
。
需要特別強調的是要記住這個函數(shù)是十分 非響應式 而且非實時的。它發(fā)起一個 HTTP 請求到 Facebook, 得到一些數(shù)據(jù), 然后作為回調函數(shù)參數(shù)返回給我們的應用程序。 但是如果 like 數(shù)改變了而這個函數(shù)不會重新運行,那么我們的界面上就無法得到當前最新數(shù)據(jù)了。
要解決這個問題,我們首先使用 setInterval
來每隔幾秒鐘調用一次這個函數(shù):
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);
任何時候當我們檢查 currentLikeCount
變量, 我們期望可以得到一個5秒鐘之內準確的數(shù)據(jù)。我們現(xiàn)在在幫助方法使用這個變量。代碼如下:
Template.postItem.likeCount = function() {
return currentLikeCount;
}
然而,我們無法每次當currentLikeCount
改變的時候重繪模板。盡管變量自己現(xiàn)在可以偽實時了,但是它不是響應式的所以無法正確地和 Meteor 生態(tài)環(huán)境中的其他部分進行溝通。
Meteor 的響應性是靠 依賴 來控制的, 就是一個跟蹤 Computation 的數(shù)據(jù)結構。
正如我們此前在響應式章節(jié)看到的, 一個 computation 是一段代碼用來處理響應式數(shù)據(jù)。我們的例子中有一個 computation 隱式的建立給 postItem
這個模板用。 這個模板中的每個幫助方法都有自己的 computation 。
你可以想象這個 computation 就是一段專門關注響應式數(shù)據(jù)的代碼。 當數(shù)據(jù)改變了, 這個 computation 就會通知 (通過 invalidate()
) , 而且也正是 computation 來決定是否有什么工作需要做。
將變量 currentLikeCount
放到一個響應式數(shù)據(jù)源中,我們需要跟蹤所有依賴這個變量的 computations.這需要把它從變量變?yōu)橐粋€函數(shù) (有返回值的函數(shù)):
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);
我們建立了一個叫 _currentLikeCountListeners
的依賴,它來跟蹤所有用到 currentLikeCount()
的 computations. 當 _currentLikeCount
值發(fā)生變化,我們通過調用依賴的 changed()
函數(shù),來通知所有 computations 數(shù)據(jù)變化了。
這些 computations 可以繼續(xù)處理下面的數(shù)據(jù)變化。
你可能覺得這像是在響應式數(shù)據(jù)源上的很多引用,你說對了,Meteor 提供很多工具使這項工作簡單 (你不需要直接調用 computations , 他們會自動運行)。有一個叫做 reactive-var
的包,它的內容正是函數(shù) currentLikeCount()
要做的事。我們加入這個包:
meteor add reactive-var
使用它可使我們的代碼簡化一點:
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);
現(xiàn)在使用這個包,我們在幫助方法中調用 currentLikeCount.get()
,它會像之前一樣工作。有另外一個有用的包 reactive-dict
, 它提供 key-value 存儲 (像 Session
一樣)。
Angular 是一個客戶端響應式庫,是 Google 的家伙們開發(fā)的。我們來比較 Meteor 和 Angular 的依賴跟蹤方式。他們的實現(xiàn)方式非常不同。
我們已經(jīng)知道 Meteor 使用一些被稱為 comptations 的代碼來實現(xiàn)依賴跟蹤的。這些 computations 被特殊的 "響應式" 數(shù)據(jù)源(函數(shù))跟蹤,在數(shù)據(jù)變化的時候將他們自己標記為 invalidate。當需要調用 invalidate()
函數(shù)時,響應式數(shù)據(jù)源_顯示的_通知所有依賴。請注意這是數(shù)據(jù)變化時的一般情況,數(shù)據(jù)源也可以因為其他原因觸發(fā) invalidation。
另外,盡管通常情況下當數(shù)據(jù) invalidate 時 computations 只是重新運行,但是你也可以在此時指定任何你想要的行為。這些給了用戶很高的響應式控制權。
在 Angular 中,響應式是通過 scope
對象來調節(jié)的。一個 scope 可以看做是擁有一些特殊方法的普通 js 對象。
當你的響應式數(shù)據(jù)依賴于 scope 中的一個值,你調用 scope.$watch
方法,告訴 expression 你關心的數(shù)據(jù)(例如: 你關心 scope 中的哪些數(shù)據(jù))和一個當 expression 發(fā)生變化時每次都運行的監(jiān)聽器。因此你需要顯示的提供當 expression 數(shù)據(jù)變化時你要做的操作。
回到之前 Facebook 的例子,我們的代碼可以寫成如下:
$rootScope.$watch('currentLikeCount', function(likeCount) {
console.log('Current like count is ' + likeCount);
});
當然,就像在 Meteor 中你很少需要去建立 computations, 在 Angular 中你無須經(jīng)常顯示調用 $watch
, ng-model
和 {{expressions}}
會自動建立跟蹤,之后當數(shù)據(jù)變化時他們會處理重新展示的事情。
當響應式數(shù)據(jù)發(fā)生變化時, scope.$apply()
方法會被調用。他會重新計算 scope 中所有的 watcher, 然后只調用 expression 值發(fā)生變化的 watcher 的監(jiān)聽器方法。
因此 scope.$apply()
方法和 Meteor 中的 dependency.changed()
很相似,除了它是在 scope 級別操作,而不是給你控制權決定哪個 listener 需要重新 evaluate。換句話說,較少的控制使得 Angular 可以通過聰明和高效的方式來決定哪些 listener 需要重新 evaluate。
在 Angular 中,我們的 getFacebookLikeCount()
函數(shù)看起來如下:
Meteor.setInterval(function() {
getFacebookLikeCount(Meteor.user(), Posts.find(postId),
function(err, count) {
if (!err) {
$rootScope.currentLikeCount = count;
$rootScope.$apply();
}
});
}, 5 * 1000);
必須承認,Meteor 替我們完成了響應式的大部分繁重工作,但是希望,通過這些模式的學習,可以對你的深入研究起到幫助。
更多建議: