了解流星发布/订阅


84

我有一个简单的应用程序设置,其中显示了的列表Projects。我已经删除了该autopublish程序包,以便不将所有内容发送给客户端。

 <template name="projectsIndex">    
   {{#each projects}}      
     {{name}}
   {{/each}}
 </template>

autopublish被打开,这将显示所有的项目:

if Meteor.isClient
  Template.projectsIndex.projects = Projects.find()

将其删除后,我还必须执行以下操作:

 if Meteor.isServer
   Meteor.publish "projects", ->
     Projects.find()
 if Meteor.isClient
   Meteor.subscribe "projects"
   Template.projectsIndex.projects = Projects.find()

那么,可以说客户端find()方法仅搜索从服务器端发布的记录是否准确?之所以让我绊倒是因为我觉得我应该只打find()一次电话。

Answers:


286

收藏,出版物和订阅是Meteor的一个棘手领域,文档可以进行更详细的讨论,以避免频繁的 混淆,有时混淆的术语加剧混淆

这是Sacha GreifDiscoverMeteor的合著者)在一张幻灯片中解释了出版物和订阅:

订阅

为了正确理解为什么需要find()多次调用的原因,需要了解集合,出版物和订阅在Meteor中的工作方式:

  1. 您在MongoDB中定义集合。尚未涉及流星。这些集合包含数据库记录(也称为“文件”并举蒙戈和流星,而是一个“文档”是不是数据库记录更普遍的;例如,更新规范或查询选择的文件太多- JavaScript对象包含field: value对)。

  2. 然后定义集合 流星服务器上使用

    MyCollection = new Mongo.Collection('collection-name-in-mongo')
    

    这些集合包含MongoDB集合中的所有数据,您可以MyCollection.find({...})在它们上运行,这将返回一个游标(一组记录,以及用于遍历它们并返回它们的方法)。

  3. 该游标(大部分时间)用于发布(发送)一组记录(称为“记录集”)。您可以选择仅发布那些记录中的某些字段。它是客户端订阅的记录集(而不是集合)。发布是通过publish函数完成的,每次新客户端订阅时都会调用该函数,并且该函数可以使用参数来管理要返回的记录(例如,用户ID,仅返回该用户的文档)。

  4. 在客户端上,您具有Minimongo集合,这些集合部分镜像了服务器中的某些记录。之所以选择“部分”是因为它们可能仅包含某些字段,而“某些记录”是因为您通常希望仅将其需要的记录发送给客户端,以加快页面加载速度,并且仅将其需要并且具有以下权限的记录发送给客户端访问。

    Minimongo本质上是纯JavaScript中Mongo的内存内非持久实现。它用作本地缓存,仅存储此客户端正在使用的数据库的子集。客户端查询(查找)直接从此缓存中提供,而无需与服务器对话。

    这些Minimongo集合最初是空的。他们被填充

    Meteor.subscribe('record-set-name')
    

    电话。注意,要订阅的参数不是集合名称。它是服务器在调用中使用的记录集的名称publish。该subscribe()调用将客户端预订到记录集-服务器集合中记录的子集(例如,最近的100个博客帖子),每个记录中包含字段的全部或子集(例如titledate)。Minimongo如何知道将传入记录放入哪个集合?集合的名称将是collection在发布处理程序的addedchangedremoved回调中使用的参数,或者,如果缺少它们(大多数情况下是这种情况),它将是服务器上MongoDB集合的名称。

修改记录

这是Meteor使事情变得很方便的地方:当您在客户端上的Minimongo集合中修改记录(文档)时,Meteor将立即更新依赖于该记录的所有模板,并将更改发送回服务器,这又将其发送回服务器。会将更改存储在MongoDB中,并将其发送给已订阅包含该文档的记录集的相应客户端。这称为延迟补偿,是流星七个核心原理之一

多个订阅

您可以有一堆订阅以获取不同的记录,但是如果订阅来自服务器上的相同集合(基于),则它们将全部位于客户端的相同集合中_id。Meteor docs对此没有明确解释,但暗含了:

订阅记录集时,它告诉服务器将记录发送到客户端。客户端存储这些记录在本地Minimongo集合,具有相同的名称作为collection中使用参数发布处理的addedchangedremoved回调。流星将对传入的属性进行排队,直到您在客户端上使用匹配的集合名称声明Mongo.Collection为止。

没有解释的是,当您根本不显式使用addedchangedremoved完全不发布处理程序时会发生什么情况-通常是在大多数时候。在这种最常见的情况下,(毫无疑问)集合参数取自在步骤1在服务器上声明的MongoDB集合的名称。但这意味着您可以使用不同的名称拥有不同的发布和订阅,并且所有记录将最终存储在客户端的同一集合中。直到顶级字段的级别,Meteor都会在文档之间执行固定的并集,以使订阅可以重叠-发布将不同的顶级字段并排发送到客户端工作的功能,并在客户端上,将集合将是两套领域的联合

示例:多个订阅填充客户端上的相同集合

您有一个BlogPosts集合,即使它做不同的事情,您也可以在服务器和客户端上以相同的方式声明它们:

BlogPosts = new Mongo.Collection('posts');

在客户端上,BlogPosts可以从以下位置获取记录:

  1. 订阅最近的10篇博客文章

    // server
    Meteor.publish('posts-recent', function publishFunction() {
      return BlogPosts.find({}, {sort: {date: -1}, limit: 10});
    }
    // client
    Meteor.subscribe('posts-recent');
    
  2. 订阅当前用户的帖子

    // server
    Meteor.publish('posts-current-user', function publishFunction() {
      return BlogPosts.find({author: this.userId}, {sort: {date: -1}, limit: 10});
      // this.userId is provided by Meteor - http://docs.meteor.com/#publish_userId
    }
    Meteor.publish('posts-by-user', function publishFunction(who) {
      return BlogPosts.find({authorId: who._id}, {sort: {date: -1}, limit: 10});
    }
    
    // client
    Meteor.subscribe('posts-current-user');
    Meteor.subscribe('posts-by-user', someUser);
    
  3. 订阅最受欢迎的帖子

  4. 等等

所有这些文档都来自postsMongoDB中的集合,通过BlogPosts服务器上的集合,最终出现在BlogPosts客户端上的集合中。

现在我们可以理解为什么您需要find()多次调用(第二次在客户端上)了,因为来自所有订阅的文档将最终位于同一集合中,并且您只需要获取您关心的那些文档即可。例如,要获取客户端上的最新帖子,您只需从服务器镜像查询:

var recentPosts = BlogPosts.find({}, {sort: {date: -1}, limit: 10});

这将使光标返回到客户到目前为止已收到的所有文档/记录,包括最高职位和用户职位。(感谢Geoffrey)。


10
这很棒。也许值得一提的是,如果您BlogPosts.find({})在订阅了两个出版物之后在客户端上进行操作,即它将返回当前客户端上所有文档/记录的光标,包括顶部的帖子和用户的帖子。我在SO上看到过其他问题,问者对此感到困惑。
杰弗里·布斯

3
这很棒。谢谢。另外,由于Meteor.users()集合在客户端自动发布,因此有点令人困惑。可以在上面的答案中添加一些内容来声明users()集合吗?
Jimmy MG Lim

3
即使比最初要求的要多得多,我认为@DVG应该将此出色的文章标记为可接受的答案。谢谢丹。
physiocoder

1
感谢@DanDascalescu,伟大的解释为我清除了很多,唯一的事情是,在阅读您的解释后遵循流星文档有关“集合”的文档时,我认为BlogPosts这不是集合,它返回的对象具有“插入”,“更新”之类的方法“ ..etc,并且真正的集合posts也在客户端和服务器中。
UXE 2014年

4
是否可以仅调用您订阅的记录集?像这样,是否可以直接在我的JavaScript中获取记录集,而不是在本地查询Minimongo数据库?
Jimmy Knoot 2014年

27

是的,客户端find()仅返回Minimongo客户端上的文档。从文档

在客户端上,将创建一个Minimongo实例。Minimongo本质上是纯JavaScript中Mongo的内存内非持久实现。它用作本地缓存,仅存储此客户端正在使用的数据库的子集。客户端查询(查找)直接从此缓存中提供,而无需与服务器对话。

正如您所说,publish()指定客户端将拥有哪些文档。


1

这里的基本经验法则是publishsubscribed客户端和服务器端的变量名应该相同。

Mongo DB和客户端上的集合名称应相同。

假设我正在使用发布和订阅名为的集合,employees那么代码看起来像


服务器端

在此,var关键字的用法是可选的(使用此关键字可使集合在此文件本地)。

CollectionNameOnServerSide = new Mongo.Collection('employees');   

Meteor.publish('employeesPubSub', function() { 
    return CollectionNameOnServerSide.find({});     
});

客户端.js文件

CollectionNameOnClientSide = new Mongo.Collection('employees');
var employeesData = Meteor.subscribe('employeesPubSub');

Template.templateName.helpers({
  'subcribedDataNotAvailable' : function(){
        return !employeesData.ready();
    },
   'employeeNumbers' : () =>{
       CollectionNameOnClientSide.find({'empId':1});
  }
});

客户端.html文件

在这里,我们可以使用subcribedDataNotAvailable助手方法来了解客户端上的数据是否准备就绪,如果数据准备就绪,则可以使用助手方法打印员工编号employeeNumbers

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.