四、控制数据流

在前一章中,我们学习了如何在数据库中持久化存储数据。 在本章中,我们将看看我们如何告诉 Meteor 发送什么给客户端。

直到现在,这一切都神奇地工作,因为我们使用了autopublish包,它与每个客户端同步所有数据。 现在,我们将手动控制这个流,只向客户机发送必要的数据。

在本章中,我们将涵盖以下主题:

同步数据-当前的网络与新的网络

在当前的 Web 中,大多数页面要么是驻留在服务器上的静态文件,要么是服务器根据请求动态生成的。 对于大多数服务器端渲染的网站来说都是如此,例如,那些用 PHP、Rails 或 Django 编写的网站。 这两种技术都不需要任何努力,除了由客户显示; 因此,它们被称为客户端。

在现代 web 应用中,浏览器的概念已经从瘦客户端转移到胖客户端。 这意味着网站的大部分逻辑驻留在客户机上,客户机请求它需要的数据。

目前,这主要通过调用 API 服务器来完成。 然后,这个 API 服务器返回数据(通常以 JSON 形式),为客户端提供了一种简单的方法来处理和适当地使用数据。

大多数现代网站都是瘦客户端和胖客户端的混合体。 正常的页面是服务器端呈现的,其中只有一些功能(如聊天框或新闻提要)是使用 API 调用更新的。

然而,Meteor 是基于这样一个理念,即使用所有客户端的计算能力比使用单个服务器更好。 一个纯胖客户端或单页应用包含了网站前端的整个逻辑,它在初始页面加载时被发送下来。

然后,服务器仅仅充当数据源,只向客户机发送数据。 这可以通过连接到一个 API 并利用 AJAX 调用来实现,或者像 Meteor 一样,使用一个名为出版/订阅的模型。 在此模型中,服务器提供一系列发布,每个客户机决定要订阅哪个数据集。

与 AJAX 调用相比,开发人员不需要考虑任何下载或上传逻辑。 Meteor 客户端同步所有数据自动在后台,只要它订阅一个特定的数据集。 当服务器上的数据发生变化时,服务器会将更新后的文档发送给客户端,反之亦然。如下图所示:

Syncing data – the current Web versus the new Web

注释

如果这听起来确实不安全,请放心,我们可以设置规则来过滤服务器端的更改。 我们将在第 8 章Security with the Allow and Deny Rules中查看这些可能性。

删除自动发布包

为了与 Meteor 的出版物/订阅合作,我们需要删除autopublish包,这是添加到我们的项目默认。

这个包对快速原型非常有用,但在生产中不可行,因为我们数据库中的所有数据都将同步到所有的客户端。 这不仅不安全,而且会减慢数据加载过程。

我们只需在终端的my-meteor-blog文件夹中运行以下命令:

$ meteor remove autopublish

现在我们可以再次运行meteor来启动服务器。 当我们查看这个网站时,我们会看到我们上一章的所有帖子都不见了。

然而,它们并没有真正消失。 当前服务器还没有发布任何内容,客户端也没有订阅任何内容; 因此,我们看不到它们。

数据发布

为了再次访问客户端上的文章,我们需要告诉服务器将其发布到订阅客户端。

为此,我们将在my-meteor-blog/server文件夹中创建一个名为publications.js的文件,并添加以下代码行:

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

Meteor.publish函数将创建一个名为all-posts的发布,并返回一个游标,该游标包含该发布中Post集合的所有发布。

现在,我们只需要告诉客户订阅这个出版物,我们就会再次看到我们的帖子。

我们在my-meteor-blog/client文件夹中创建一个名为subscriptions.js的文件,内容如下:

Meteor.subscribe('all-posts');

现在,当我们查看我们的网站时,我们可以看到我们的博客文章重新出现了。

这是因为当subsciptions.js文件被执行时,客户端将订阅all-posts出版物,这恰好发生在页面完全加载之前,Meteor 自动将subsciptions.js文件添加到我们的文档头部。

这意味着 Meteor 服务器首先发送网站,JavaScript 在客户端构建 HTML; 然后,同步所有订阅,填充客户端的集合,模板引擎Blaze可以显示帖子。

现在我们已经返回了我们的帖子,让我们看看如何让 Meteor 只发送集合中的文档子集。

只发布部分数据

为了使我们的首页页面为未来做好准备,我们需要限制页面上显示的帖子数量,因为随着时间的推移,我们可能会添加很多帖子。

为此,我们将创建一个名为limited-posts的新发布,在这里我们可以向 posts 的find()函数传递limit选项,并将其添加到我们的publications.js文件中,如下所示:

Meteor.publish('limited-posts', function () {
  return Posts.find({}, {
    limit: 2,
    sort: {timeCreated: -1}
  });
});

我们添加了一个sort选项,用它我们在timeCreated字段上按降序排列文章。 这是必要的,以确保我们得到最新的帖子,然后限制输出。 如果我们只对客户机上的数据进行排序,可能会遗漏较新的文章,因为服务器发布只发送它找到的前两个文档,而不管它们是否是最新的。

现在我们只需要进入subscriptions.js并将订阅更改为以下代码行:

Meteor.subscribe('limited-posts');

如果我们现在打开浏览器,我们会看到首页上只出现了最后两篇文章,因为我们只订阅了两篇,如下截图所示:

Publishing only parts of data

注释

我们必须意识到,如果我们将旧订阅的代码与新订阅的代码一起保留,我们将同时订阅两者。 这意味着 Meteor 合并了两种订阅,因此将所有订阅的文档保存在我们的客户端集合中。

在添加新订阅之前,我们需要注释掉旧订阅或删除它。

发布特定领域

为了改进发布,我们还可以确定要从文档中发布哪些字段。 例如,我们只能要求titletext属性,而不能要求所有其他属性。

这加快了订阅的同步,因为我们不需要整个帖子,而只需要必要的数据和简短的描述,当在首页列出帖子时。

让我们添加另一个发布到我们的publications.js文件:

Meteor.publish('specificfields-posts', function () {
  return Posts.find({}, {
    fields: {
      title: 1
    }
  });
});

由于这只是一个示例,我们传递一个空对象作为查找所有文档的查询,并且作为find()的第二个参数,我们传递一个包含fields对象的选项对象。

我们给出的值为1的每个字段都将包含在返回的文档中。 如果希望排除字段,可以使用字段名并将值设置为0。 但是,我们不能同时使用包含字段和排除字段,因此需要根据文档大小选择更适合的字段。

现在,我们可以简单地将subscriptions.js文件中的订阅更改为以下代码行:

Meteor.subscribe('specificfields-posts');

现在,当我们打开浏览器时,它会显示我们的文章列表。 只有标题是,描述、时间和作者字段是空的:

Publishing specific fields

惰性加载桩

现在我们已经完成了和这些简单的例子,让我们把它们放在一起,并在首页的帖子列表中添加一个漂亮的延迟加载特性。

延迟加载是一种只在用户需要或滚动到结束时加载额外数据的技术。 这可以用来增加页面加载,因为要加载的数据是有限的。 要做到这一点,让我们执行以下步骤:

  1. 我们需要在首页的帖子列表的底部添加一个延迟加载按钮。 我们进入我们的home.html文件,并在home模板的末尾添加以下按钮,就在{{#each postsList}}块助手的正下方:

    <button class="lazyload">Load more</button>

  2. 接下来,我们将添加发布,将发送灵活数量的文章到我们的publications.js文件,如下所示:

基本上,这是我们之前学到的知识的结合。

  • 我们使用了limit选项,但是我们没有设置一个固定的数字,而是使用了limit参数,稍后我们将把它传递给这个发布函数。
  • 以前,我们使用fields选项并排除text字段。
  • 我们可以只包含fields来得到相同的结果。 这将是更安全的,因为它确保我们不会得到任何额外的字段,以防文档被扩展:
  • 我们对输出进行了排序,以确保我们总是返回最新的帖子。

现在我们已经设置了发布,让我们添加一个订阅,以便能够接收它的数据。

注释

请注意,我们需要提前删除任何其他订阅,这样我们就不会订阅任何其他发布。

为了做到这一点,我们需要利用 Meteor 的session对象。 该对象可用于客户端响应式设置变量。 这意味着每次我们改变这个会话的变量时,它都会再次运行使用它的每个函数。 在下面的例子中,我们将使用 session 来增加我们的 posts' lists'数量,当点击 lazy load 按钮:

  1. 首先,在subscription.js文件中,我们添加以下代码行:
  2. 然后我们将会话变量lazyloadLimit设置为2,这将是首页显示的文章的初始数量。
  3. 接下来,我们创建一个Tracker.autorun()函数。 这个函数将在开始时间运行,然后在我们将lazyloadLimit会话变量更改为另一个值时运行。
  4. 在这个函数中,我们订阅lazyload-posts,并将lazyloadLimit值作为第二个参数。 这样,每当会话变量更改时,我们就用一个新值更改订阅。
  5. Now we only need to increase the session value by clicking on the lazy load button and the subscription will change, sending us additional posts. To do this, we add the following lines of code to our home.js file at the end:

    ``` Template.home.events({ 'click button.lazyload': function(e, template){ var currentLimit = Session.get('lazyloadLimit');

    Session.set('lazyloadLimit', currentLimit + 2); } }); ```

    这段代码将在惰性加载按钮上附加一个click事件。 每次我们点击这个按钮,我们得到lazyloadLimit会话,它增加 2。

  6. 当我们查看浏览器时,我们应该能够点击帖子列表底部的惰性加载按钮,它应该会添加另外两篇帖子。 每当我们点击按钮时,这种情况就会发生,直到我们看到 5 篇示例文章。

当我们只有 5 篇文章时,这并没有多大意义,但当有超过 50 篇文章时,将最初显示的文章限制在 10 篇将显著加快页面加载时间。

然后我们需要将会话的默认值改为,并将其增加 10,这样就有了一个很好的延迟加载效果。

切换订阅

现在我们已经有了这个很好的延迟加载逻辑,让我们看看下面发生了什么。

我们前面创建的.autorun()函数将在代码第一次执行时运行,订阅我们的lazyload-posts发布。 然后 Meteor 发送Posts系列的前两个文档,因为我们第一次发送的limit2

下一次更改lazyloadLimit会话时,它将通过更改传递给发布函数的值的限制来更改订阅。

Meteor 然后在后台检查客户端数据库中存在哪些文件,并请求下载缺失的文件。

当我们减少会话值时,这也会以另一种方式工作。 Meteor 删除与当前订阅/订阅不匹配的文档。

我们可以试试这个; 打开浏览器控制台,将会话限制设置为5:

Session.set('lazyloadLimit', 5);

这将立即显示列表中的所有 5 个示例文章。 当我们现在将它设置回一个更小的值时,我们将看到它们是如何被删除的:

Session.set('lazyloadLimit', 2);

为了确保它们消失了,我们可以查询本地数据库进行检查,如下所示:

Posts.find().fetch();

这将返回给我们一个两个项目的数组,显示 Meteor 删除了我们不再订阅的帖子,如下截图所示:

Switching subscriptions

关于数据发布的几点注意事项

发布和订阅模型使向客户机接收和发送数据变得相当容易,但与对服务器的每次调用一样,发送和请求数据的开销很大,因为服务器和客户机都必须处理请求。 因此,在开发应用时要牢记以下几点:

  • 只订阅那些在屏幕上显示所需的文档。
  • 避免在不需要的时候发送内容较大的字段。 这使得我们的数据流更加精简和快速。
  • 如果我们在我们的出版物中使用limitskip,我们需要确保我们在服务器上对它进行排序,以便我们首先得到所需的数据,而不是它的错误尾巴。

您还应该知道,Meteor.publish()函数不是反应式的。 这意味着你不能像在客户端上做的那样,让一个光标依赖于另一个光标的结果。 例如,以下代码片段将无法工作,因为当Posts集合中的注释计数发生变化时,它将永远不会重新运行:

Meteor.publish('comments', function (postId) {
    var post = Posts.find({_id: postId});

    return Comments.find({_id: {$in: post.comments}});
});

为了解决这个问题,你可以单独发表文章和评论,并将他们连接在客户端或使用第三方包,它允许等活性出版物伟大的reywood:publish-composite包在 https://atmospherejs.com/reywood/publish-composite。

注释

请注意,Meteor.publish()函数唯一会重新运行的情况是:当前用户发生变化,因此在该函数中可访问的this.userId将发生变化。

小结

在本章中,我们创建了一些出版物并订阅了它们。 我们使用了fieldslimit选项来修改已发布文档的数量,并为我们的博客首页创建了一个简单的延迟加载逻辑。

为了更深入地学习,我们可以看一下第 3 章存储数据和处理集合。 而以下 Meteor 文件将给我们详细的选项,我们可以在集合find()功能:

你可以在https://www.packtpub.com/books/content/support/17713或在 GitHub 上https://github.com/frozeman/book-building-single-page-web-apps-with-meteor/tree/chapter4找到本章的代码示例。

在下一章中,我们将告诉我们的应用是怎样形成一个真正的应用的——不同的页面和路由。