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