了解 Meteor 发布/订阅

IT技术 javascript mongodb meteor publish-subscribe
2021-01-30 09:27:35

我有一个简单的应用程序设置,显示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()一次电话

4个回答

收藏、出版物和订阅是 Meteor 的一个棘手领域,文档可以更详细地讨论,以避免经常 混淆,有时会因混淆术语而被放大

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

订阅

要正确理解为什么需要find()多次调用,您需要了解 Meteor 中的集合、发布和订阅是如何工作的:

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

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

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

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

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

  4. 在客户端,您有Minimongo集合,它们部分地反映来自服务器的一些记录。“部分”是因为它们可能只包含部分字段,而“部分记录”是因为您通常只想将其需要的记录发送给客户端,以加快页面加载速度,并且只向客户端发送它需要有权限的记录使用权。

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

    这些 Minimongo 集合最初是空的。他们充满了

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

    调用。请注意,要订阅的参数不是集合名称;它是服务器在调用中使用记录集的名称publishsubscribe()调用将客户端订阅到一个记录集——来自服务器集合的一个记录子集(例如最近的 100 篇博客文章),每个记录中包含所有或一部分字段(例如,只有titledate)。Minimongo 如何知道将传入的记录放入哪个集合?集合的名称将是collection发布处理程序的addedchangedremoved回调中使用参数,或者如果缺少这些参数(大多数情况下都是这种情况),它将是服务器上 MongoDB 集合的名称。

修改记录

这就是 Meteor 使事情变得非常方便的地方:当您在客户端修改 Minimongo 集合中的记录(文档)时,Meteor 将立即更新所有依赖它的模板,并将更改发送回服务器,服务器反过来将更改存储在 MongoDB 中,并将它们发送给订阅了包含该文档的记录集的相应客户端。这称为延迟补偿,是Meteor七项核心原则之一

多个订阅

您可以有一堆订阅来获取不同的记录,但是如果它们来自服务器上的同一个集合,则它们最终都会在客户端上的同一个集合中,基于它们的_id. 这没有解释清楚,但由 Meteor 文档暗示:

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

没有解释的是当您根本不显式使用added, changedand removed, 或发布处理程序时会发生什么- 这是大多数情况下。在这种最常见的情况下,集合参数(不出所料)取自您在第 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});

这将返回一个光标,指向客户迄今为止收到的所有文档/记录,包括热门帖子和用户的帖子。感谢杰弗里)。

这很棒。也许值得一提的是,如果您BlogPosts.find({})在订阅了这两个出版物后在客户端上执行此操作会发生什么——即它将返回当前在客户端上的所有文档/记录的游标,包括热门帖子和用户的帖子。我在 SO 上看到了其他问题,提问者对此感到困惑。
2021-03-20 09:27:35
谢谢@DanDascalescu,很好的解释让我明白了很多,在阅读了你的解释后,在关注关于“集合”的流星文档时唯一的事情我认为BlogPosts不是集合,它返回的对象具有“插入”、“更新”等方法“ .. 等,真正的集合posts也在客户端和服务器中。
2021-03-27 09:27:35
这很棒。谢谢。此外, Meteor.users() 集合有点令人困惑,因为它是在客户端自动发布的。可以在上面的答案中添加一点来说明 users() 集合吗?
2021-04-05 09:27:35
即使比最初要求的多得多,我认为@DVG 应该将这篇出色的文章标记为已接受的答案。谢谢丹。
2021-04-05 09:27:35
是否可以只调用您订阅的记录集?如在,是否可以直接在我的 javascript 中获取记录集,而不是在本地查询 Minimongo 数据库?
2021-04-06 09:27:35

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

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

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

这里的基本经验法则是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 文件

这里我们可以使用subcribedDataNotAvailablehelper 方法来知道客户端数据是否准备好,如果数据准备好,则使用employeeNumbershelper 方法打印员工编号

<TEMPLATE name="templateName">
{{#if subcribedDataNotAvailable}}
   <h1> data loading ... </h1>
 {{else}}
  {{#each employeeNumbers }}
     {{this}}
  {{/each}}
 {{/if}}
<TEMPLATE>
// on the server
Meteor.publish('posts', function() {

    return Posts.find();

});

// on the client
Meteor.subscribe('posts');