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