鍍金池/ 教程/ HTML/ 高級的響應性
投票
發(fā)布和訂閱
添加用戶
簡介
會話
錯誤
開始
路由
高級的響應性
編輯帖子
響應式
動畫
部署
Notifications
允許與拒絕
評論
創(chuàng)建帖子
非規(guī)范化
高級發(fā)布機制
使用 Git 和 GitHub
更進一步
延時補償
集合
分頁
創(chuàng)建 Meteor Package
模版

高級的響應性

雖然需要你自己寫代碼來跟蹤依賴變量的情況十分罕見,了解依賴變量的工作流程還是十分必要的。

設想我們現(xiàn)在需要跟蹤一下 Microscope上,當前用戶的 Facebook 朋友在 “l(fā)ike” 某一篇帖子的數(shù)量。 讓我們假設我們已經(jīng)解決了 Facebook 用戶認證的問題,運用了正確的 API 調(diào)用,而且也解析了相關(guān)數(shù)據(jù)。 我們現(xiàn)在有一個異步的客戶端函數(shù)返回 like 的數(shù)量,getFacebookLikeCount(user, url, callback)

需要特別強調(diào)的是要記住這個函數(shù)是十分 非響應式 而且非實時的。它發(fā)起一個 HTTP 請求到 Facebook, 得到一些數(shù)據(jù), 然后作為回調(diào)函數(shù)參數(shù)返回給我們的應用程序。 但是如果 like 數(shù)改變了而這個函數(shù)不會重新運行,那么我們的界面上就無法得到當前最新數(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);

任何時候當我們檢查 currentLikeCount 變量, 我們期望可以得到一個5秒鐘之內(nèi)準確的數(shù)據(jù)。我們現(xiàn)在在幫助方法使用這個變量。代碼如下:

Template.postItem.likeCount = function() {
  return currentLikeCount;
}

然而,我們無法每次當currentLikeCount 改變的時候重繪模板。盡管變量自己現(xiàn)在可以偽實時了,但是它不是響應式的所以無法正確地和 Meteor 生態(tài)環(huán)境中的其他部分進行溝通。

Tracking Reactivity: Computations

Meteor 的響應性是靠 依賴 來控制的, 就是一個跟蹤 Computation 的數(shù)據(jù)結(jié)構(gòu)。

正如我們此前在響應式章節(jié)看到的, 一個 computation 是一段代碼用來處理響應式數(shù)據(jù)。我們的例子中有一個 computation 隱式的建立給 postItem 這個模板用。 這個模板中的每個幫助方法都有自己的 computation 。

你可以想象這個 computation 就是一段專門關(guān)注響應式數(shù)據(jù)的代碼。 當數(shù)據(jù)改變了, 這個 computation 就會通知 (通過 invalidate()) , 而且也正是 computation 來決定是否有什么工作需要做。

將變量變?yōu)轫憫胶瘮?shù)

將變量 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ā)生變化,我們通過調(diào)用依賴的 changed() 函數(shù),來通知所有 computations 數(shù)據(jù)變化了。

這些 computations 可以繼續(xù)處理下面的數(shù)據(jù)變化。

你可能覺得這像是在響應式數(shù)據(jù)源上的很多引用,你說對了,Meteor 提供很多工具使這項工作簡單 (你不需要直接調(diào)用 computations , 他們會自動運行)。有一個叫做 reactive-var 的包,它的內(nèi)容正是函數(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)在使用這個包,我們在幫助方法中調(diào)用 currentLikeCount.get(),它會像之前一樣工作。有另外一個有用的包 reactive-dict, 它提供 key-value 存儲 (像 Session 一樣)。

Comparing Tracker to Angular

Angular 是一個客戶端響應式庫,是 Google 的家伙們開發(fā)的。我們來比較 Meteor 和 Angular 的依賴跟蹤方式。他們的實現(xiàn)方式非常不同。

我們已經(jīng)知道 Meteor 使用一些被稱為 comptations 的代碼來實現(xiàn)依賴跟蹤的。這些 computations 被特殊的 "響應式" 數(shù)據(jù)源(函數(shù))跟蹤,在數(shù)據(jù)變化的時候?qū)⑺麄冏约簶擞洖?invalidate。當需要調(diào)用 invalidate() 函數(shù)時,響應式數(shù)據(jù)源_顯示的_通知所有依賴。請注意這是數(shù)據(jù)變化時的一般情況,數(shù)據(jù)源也可以因為其他原因觸發(fā) invalidation。

另外,盡管通常情況下當數(shù)據(jù) invalidate 時 computations 只是重新運行,但是你也可以在此時指定任何你想要的行為。這些給了用戶很高的響應式控制權(quán)。

在 Angular 中,響應式是通過 scope 對象來調(diào)節(jié)的。一個 scope 可以看做是擁有一些特殊方法的普通 js 對象。

當你的響應式數(shù)據(jù)依賴于 scope 中的一個值,你調(diào)用 scope.$watch 方法,告訴 expression 你關(guān)心的數(shù)據(jù)(例如: 你關(guān)心 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)常顯示調(diào)用 $watch, ng-model{{expressions}} 會自動建立跟蹤,之后當數(shù)據(jù)變化時他們會處理重新展示的事情。

當響應式數(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);

必須承認,Meteor 替我們完成了響應式的大部分繁重工作,但是希望,通過這些模式的學習,可以對你的深入研究起到幫助。

上一篇:高級發(fā)布機制下一篇:集合