Meteor 高級(jí)的響應(yīng)性

2022-06-30 13:59 更新

高級(jí)的響應(yīng)性

雖然需要你自己寫代碼來(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)行溝通。

Tracking Reactivity: Computations

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)決定是否有什么工作需要做。

將變量變?yōu)轫憫?yīng)式函數(shù)

將變量 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 一樣)。

Comparing Tracker to Angular

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ì)你的深入研究起到幫助。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)