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

高級發(fā)布機制

目前你應該對發(fā)布和訂閱交互模式有一個不錯的掌握了。因此,我們廢話少說,來看幾個更高級的情景。

多次發(fā)布一個集合

在我們第一個關(guān)于發(fā)布的附錄中,我們看到了一些更普遍的發(fā)布和訂閱模式,同時我們學習了 _publishCursor 函數(shù),如何讓它們非常容易地實現(xiàn)在我們的站點上。

首先,讓我們回憶 _publishCursor 到底為我們做了什么:它將整理所有的文檔以匹配一個給定的游標(cursor),并將它們推送至同名的客戶端集合中。注意這與 publication 的名字是不關(guān)聯(lián)的。

這意味著我們可以用不止一個 publicaton 去連接任何集合的客戶端與服務端版本。

我們已經(jīng)在分頁章節(jié)用過這個模式,當我們在當前顯示的帖子之外,再發(fā)布一個所有帖子的分頁后的子集。

另一個相似的用例是發(fā)布一大組文檔的預覽,和單個文檔的全部信息:

http://wiki.jikexueyuan.com/project/discover-meteor/images/doublecollection@2x.png" alt="" />

Meteor.publish('allPosts', function() {
  return Posts.find({}, {fields: {title: true, author: true}});
});

Meteor.publish('postDetail', function(postId) {
  return Posts.find(postId);
});

現(xiàn)在客戶端訂閱這兩個發(fā)布,這 'posts' 集合來自于兩個源渠道:來自第一個訂閱的標題和作者姓名列表,和來自第二個訂閱的單個帖子全部信息。

你也許意識到了 postDetail 發(fā)布的帖子也被 allPosts 發(fā)布了(盡管只有它的部分屬性)。但是,Meteor 會合并字段及確認沒有重復的帖子,來處理數(shù)據(jù)重疊的問題。

這是很棒的,因為現(xiàn)在當我們呈現(xiàn)帖子摘要列表時,我們正在處理的數(shù)據(jù)對象正好擁有我們需要顯示的足夠數(shù)據(jù)。但是,當我們呈現(xiàn)單個帖子時,我們有一切需要展示的數(shù)據(jù)。當然,在這種情況下,我們需要讓客戶端不要去期待所有帖子的所有字段都能都顯示出來————這是一個常見的問題!

注意你并沒有改變文檔屬性的任何限制。你可以很好地在這兩個發(fā)布中發(fā)布同樣的屬性,但是先后排序不同。

Meteor.publish('newPosts', function(limit) {
  return Posts.find({}, {sort: {submitted: -1}, limit: limit});
});

Meteor.publish('bestPosts', function(limit) {
  return Posts.find({}, {sort: {votes: -1, submitted: -1}, limit: limit});
});

多次訂閱一個發(fā)布

我們已經(jīng)看了如何多次發(fā)布同一個集合。事實證明你可以通過另一個模式來完成非常相近的結(jié)果:建立一個單一發(fā)布,卻多次訂閱它。

在 Microscope 中,我們多次重復訂閱 posts 發(fā)布,但 Iron Router 為我們設置并拆開每次的訂閱。然而,沒有理由我們不能同時進行多次訂閱。

舉個例子,我們想要將最新的和最好的帖子同時載入內(nèi)存:

http://wiki.jikexueyuan.com/project/discover-meteor/images/subscribetwice@2x.png" alt="" />

我們設定一個單一發(fā)布:

Meteor.publish('posts', function(options) {
  return Posts.find({}, options);
});

并且我們多次訂閱這個發(fā)布。事實上或多或少我們在 Microscope 里這樣做了:

Meteor.subscribe('posts', {submitted: -1, limit: 10});
Meteor.subscribe('posts', {baseScore: -1, submitted: -1, limit: 10});

接下來到底發(fā)生什么了?每個瀏覽器開啟了兩個不同的訂閱,每個訂閱連接到同個服務端的發(fā)布。

每個訂閱提供了不同的發(fā)布參數(shù),但從根本上,每次一個(不同)文檔子集從 posts 集合提取出來,并通過連接機制發(fā)送到客戶端集合。

你甚至可以用同樣的參數(shù)訂閱兩次相同的發(fā)布。這個對很多處場景來說很難說有用,但這種彈性機制總有一天會有用的。

單一訂閱中的多個集合

不像傳統(tǒng)關(guān)系型數(shù)據(jù)庫像 MySQL 使用 joins,NoSQL 數(shù)據(jù)庫類似 Mongo 都是關(guān)于去規(guī)范化嵌入。讓我們看看它們是怎樣在 Meteor 環(huán)境下工作的。

讓我們看一個具體的例子。我們已經(jīng)對我們的帖子添加了評論,到目前為止,我們一直很愉快地只發(fā)布用戶看的單個帖子的評論。

但是,假設我們希望在首頁中顯示全部帖子的回復(記著這些帖子會在分頁時被改變)。這個用例展示了一個很好的理由把評論嵌入帖子中,事實上這是促使我們來非規(guī)范化評論數(shù)量。

當然我們可以總是嵌入評論到帖子中,并完全摒除 Comments 集合。但如同我們前面在去規(guī)范化章節(jié)看到的,我們將在分離的集合的操作中也會失去一些額外的好處。

但是事實證明有一個涉及訂閱的技巧,在保持分離集合的同時再嵌入我們的評論。

讓我們假定除了首頁帖子列表之外,我們希望再訂閱每個帖子的兩個最新評論。

使用獨立的評論發(fā)布會很難完成這個要求,尤其在帖子列表受到某些限制時(比如說,最近的10個)。我們必須寫一個發(fā)布,看起來像下面的代碼:

http://wiki.jikexueyuan.com/project/discover-meteor/images/multiplecollections@2x.png" alt="" />

Meteor.publish('topComments', function(topPostIds) {
  return Comments.find({postId: topPostIds});
});

從性能角度來看這是個問題,因為這個發(fā)布將需要每次在 topPostIds 改變時消除及重新建立。

有一個途徑來解決這個問題。我們可以應用這個事實:就是我們不僅可以在每個集合上擁有多次發(fā)布,而且我們也可以在每個發(fā)布上擁有多個集合。

Meteor.publish('topPosts', function(limit) {
  var sub = this, commentHandles = [], postHandle = null;

  // send over the top two comments attached to a single post
  function publishPostComments(postId) {
    var commentsCursor = Comments.find({postId: postId}, {limit: 2});
    commentHandles[postId] =
      Mongo.Collection._publishCursor(commentsCursor, sub, 'comments');
  }

  postHandle = Posts.find({}, {limit: limit}).observeChanges({
    added: function(id, post) {
      publishPostComments(id);
      sub.added('posts', id, post);
    },
    changed: function(id, fields) {
      sub.changed('posts', id, fields);
    },
    removed: function(id) {
      // stop observing changes on the post's comments
      commentHandles[id] && commentHandles[id].stop();
      // delete the post
      sub.removed('posts', id);
    }
  });

  sub.ready();

  // make sure we clean everything up (note `_publishCursor`
  //   does this for us with the comment observers)
  sub.onStop(function() { postHandle.stop(); });
});

注意我們在這個發(fā)布中沒有返回任何東西,因為我們自己手動給 sub 發(fā)送信息(通過 .added() 等方式)。所以我們不必通過返回一個游標來請求 _publishCursor 給我們來做這個動作。

現(xiàn)在,每次我們發(fā)布一個帖子時,我們也自動發(fā)布其2個最新評論。而且所有都在一個訂閱調(diào)用中!

雖然 Meteor 還未直接實現(xiàn)這個方法,但是你也可以參考在 Atomsphere 里的 publish-with-relations 包,它的目標就是讓這個模式更容易使用。

連接不同的集合

這樣的訂閱彈性機制還能給我們更多新知識么?當然,如果我們不使用 _publishCursor,我們不必跟著此項約束,就是在服務端的源集合需要與客戶端的目標集合有同樣的名稱。

http://wiki.jikexueyuan.com/project/discover-meteor/images/linkedcollections@2x.png" alt="" />

為什么我們想這么做的一個原因就是單表繼承。

假設我們需要從我們的帖子中引用多種類型的對象,每一個對象存儲在相同字段中但又顯然是不同內(nèi)容。例如,我們建立一個類似 Tumblr 的博客引擎,每個帖子具有常見的 ID、時間戳,以及標題;但是額外也有如圖像、視頻、鏈接,或者只是文字。

我們可以將這些對象存儲在一個單獨的 'resources' 集合中,使用 type 屬性來標記他們是什么類型的對象(video、image、link 等等)。

同時,雖然我們有一個服務端的單一的 Resources 集合,我們也能夠?qū)我患限D(zhuǎn)換到多個的 Videos、`Images',等等集合中。 客戶端的集合如下的代碼:

  Meteor.publish('videos', function() {
    var sub = this;

    var videosCursor = Resources.find({type: 'video'});
    Mongo.Collection._publishCursor(videosCursor, sub, 'videos');

    // _publishCursor doesn't call this for us in case we do this more than once.
    sub.ready();
  });

我們告訴 _publishCursor (就像返回)游標會做的一樣發(fā)布我們的視頻,但不是將 resources 集合發(fā)布到客戶端,而是我們從 resources 發(fā)布到 videos

另一個類似的主意是:發(fā)布到客戶端的集合,卻根本沒有服務端集合!舉例,你也許從一個第三方服務中抓取數(shù)據(jù),并發(fā)布它們到一個客戶端集合。

由于發(fā)布 API 的靈活性,可能性是無限的。

上一篇:延時補償下一篇:高級的響應性