Meteor 教程:使用 Meteor 构建实时 Web 应用程序
已发表: 2022-03-11Meteor 是一个用于 Web 和移动应用程序的全栈 JavaScript 框架。 它自 2011 年问世以来,在 Meteor 开发人员中享有盛誉,因为它是一种理想的、易于使用的快速原型设计解决方案。 然而,最近开发人员意识到 Meteor 不再只是用于原型设计:它已准备好用于商业开发。 凭借它提供的软件包库、它所依赖的坚实的 mongoDB/node.js 基础以及它提供的编码灵活性; Meteor 可以轻松构建强大、安全的实时 Web 应用程序,处理从浏览器应用程序到服务器或数据库的所有内容。
这个 Meteor 教程将引导您在 Meteor 中制作一个基本的 Web 应用程序 - 一个简单的目录,让用户可以登录和管理书籍列表。
为什么使用流星? 简短的回答是,“因为 Meteor 很有趣”。 它使开发 Web 应用程序变得简单。 它很容易学习,它让您可以更多地关注应用程序的功能,而不是同步数据和服务页面的基础知识。
它还方便地内置了许多行为。Meteor 自动执行实时更新,因此数据更改会立即显示在您的浏览器窗口中,甚至应用程序本身的代码更改也会“实时”推送到所有浏览器和设备。 Meteor 具有内置的延迟补偿,易于部署,并且具有易于安装的“包”,可以处理各种功能。
尽管它是一个相对较新的框架,但许多初创公司已经在构建 Meteor 应用程序,包括 Respondly 和 Telescope 等相对大规模的服务。
流星安装和脚手架
在 *nix 系统上安装 Meteor 是单行的:
curl https://install.meteor.com/ | sh
尽管仍然没有官方支持,但他们的 Windows 预览版进展顺利。 有传言说,Windows 支持将在 2015 年 4 月或 5 月发布的 1.1 中提供。正如您对 Meteor 等智能框架所期望的那样,引导应用程序需要调用一行命令:
meteor create book-list
这将创建一个名为“book-list”的目录,并用一些样板和相关代码填充它。 要运行应用程序,请进入新创建的目录并执行:
meteor
在您的网络浏览器中打开http://localhost:3000
,您将看到以下内容:
您还可以在 MeteorPad 上查看我们应用程序的“版本 0”,该站点类似于 JSFiddle for Meteor:图书列表:默认应用程序
Meteor 将其视图存储在 HTML 文件中。 如果我们打开“book-list.html”,我们将看到:
<head> <title>book-list</title> </head> <body> <h1>Welcome to Meteor!</h1> {{> hello}} </body> <template name="hello"> <button>Click Me</button> <p>You've pressed the button {{counter}} times.</p> </template>
Meteor 使用模板引擎“Blaze”来呈现来自这些 HTML 文件的响应。 使用过 Handlebars.js(或其他类似模板引擎)的任何人都应该熟悉双大括号,它们在这里提供类似的功能。 Blaze 检查每对双括号内的表达式,并将每个表达式替换为这些表达式产生的值。
这个简单的示例程序只有两个双括号表达式:
第一个“{{> hello}}”告诉 Blaze 包含一个名为“hello”的模板。 该模板在文件底部的 <template name=”hello”> 部分中定义。
第二个,“{{counter}}”,稍微复杂一些。 要查看这个“计数器”值的来源,我们需要打开“book-list.js”:
if (Meteor.isClient) { // counter starts at 0 Session.setDefault('counter', 0); Template.hello.helpers({ counter: function () { return Session.get('counter'); } }); Template.hello.events({ 'click button': function () { // increment the counter when button is clicked Session.set('counter', Session.get('counter') + 1); } }); } if (Meteor.isServer) { Meteor.startup(function () { // code to run on server at startup }); }
有些事情需要在这里解释。 首先,拆分为“if(Meteor.isClient)”和“if(Meteor.isServer)”。 回想一下,meteor 是一个全栈框架,因此您编写的代码在服务器和客户端上都运行。 这些条件允许我们限制:第一个块只在客户端运行,第二个只在服务器上运行。
其次,有“Session.setDefault”调用——这会在浏览器中初始化一个名为“counter”的会话变量。 会话变量的行为有点像 Meteor 中的全局变量(无论好坏)。 但是,该会话变量并未直接显示在“{{counter}}”中。 相反,那个“counter”表达式是一个“helper”,它在“Template.hello.helpers”部分中定义。 这个助手简单地获取会话变量的值并返回它。
请注意,助手是“反应式的”。 这意味着每当会话变量发生变化时,Meteor 都会自动重新运行引用它的辅助函数,并且 Blaze 会自动使用新内容更新浏览器。
客户端代码还通过“Template.hello.events”监控事件。 我们通过事件类型和选择器(在本例中为“单击按钮”)识别事件,然后告诉 Meteor 该事件应该做什么。 在这种情况下,会话变量会递增,这会重新运行辅助函数并依次重新呈现内容。
显示静态数据
所有这一切都很好,但它不是我们本教程想要的 Meteor 应用程序。
让我们开始调整这个应用程序——我们将显示一个静态的、硬编码的书籍列表。 现在,我们将书籍列表存储在会话变量中。 在“isClient”代码中,一旦 bookList 模板被渲染,我们将使用“Template.hello.rendered”设置会话变量:
Template.hello.rendered = function() { Session.setDefault('books', [ {title: "To Kill a Mockingbird", author: "Harper Lee"}, {title: "1984", author: "George Orwell"}, {title: "The Lord of the Rings", author: "JRR Tolkien"}, {title: "The Catcher in the Rye", author: "JD Salinger"}, {title: "The Great Gatsby", author: "F. Scott Fitzgerald"} ]); };
然后,我们在“hello”模板中返回带有新助手的会话变量:
Template.hello.helpers({ books: function () { return Session.get('books'); } });
并通过“hello”模板中的变量插值将其显示在屏幕上:
<template name="hello"> <h3>Here are your books:</h3> {{ books }} </template>
您可以在 Meteorpad 上查看此代码:Book-List: Show Session Variable
首先要注意的是,Meteor 服务器自动检测到我们代码库的更改,将新代码推送到客户端,并提示客户端重新加载。 即使在我们部署了应用程序之后,我们也可以部署更改并通过热代码推送自动更新我们的客户端。
到目前为止,这是我们得到的:
糟糕,我们显示的数据有误。 Blaze 在这里获得了准确性的分数(为什么是的,它是一个对象数组),但是如果我们想以一种有用的方式显示我们的书籍列表,我们将不得不更聪明一点。 幸运的是,Blaze 使用“#each”指令使处理数据数组变得非常容易:
<h3>Here are your books:</h3> <UL> {{#each books}} <LI><i>{{title}}</i> by {{author}}</LI> {{/each}} </UL>
在 Blaze 中,“#each”的工作方式有点像 Angular 的“ng-repeat”指令——它遍历数组结构,将当前上下文设置为数组中的当前对象,并在“{{#each”中重复显示 HTML …}}”。 这是我们现在的书单的样子:
在 MeteorPad 上:正确显示会话变量
一些清理
在继续之前,让我们稍微清理一下我们的代码。
Meteor 为您组织代码库的方式提供了巨大的回旋余地。 正如您将看到的,只有一些硬性规定:无论您将 HTML 和 JavaScript 放在哪里,Meteor 都会找到它。 这种灵活性很好,但它确实意味着你更有责任以一种有意义的方式组织你的代码,这样你就不会被困在维护一个巨大的无序混乱中。
首先,让我们将这个“hello”模板重命名为有意义的名称,例如“bookList”,并将样板 HTML 替换为:
<head> <title>book-list</title> </head> <body> {{> bookList}} </body> <template name="bookList"> <h3>Here are some books:</h3> <UL> {{#each books}} <LI><i>{{title}}</i> by {{author}}</LI> {{/each}} </UL> </template>
其次,让我们将“客户端”和“服务器”部分拆分为单独的文件。 在我们的应用程序目录中,我们将设置一个“client”子目录和一个“server”子目录 - Meteor 会自动知道在客户端运行“/client/”文件,并在客户端运行“/server/”文件服务器。 将模板代码放在以模板命名的 JavaScript 文件中是一个很好的约定,所以让我们将客户端代码放在“client/bookList.js”中。 我们可以将当前空的服务器启动代码放在“server/startup.js”中。 最后,让我们搬出“ ”模板代码到“client/bookList.html”。
请注意,即使在所有这些切换之后,Meteor 仍然会自动找到我们所有的 HTML 和 JavaScript 文件。 只要文件位于“/client/”中的某个位置,Meteor 就会知道在客户端上运行它。 只要文件位于“/server/”中的某个位置,meteor 就会知道在服务器上运行它。 同样,由开发人员决定是否有条理。
所以现在我们的代码应该是这样的:
书-list.html:
<head> <title>book-list</title> </head> <body> {{> bookList}} </body>
客户端/bookList.html:
<template name="bookList"> <h3>Here are some books:</h3> <UL> {{#each books}} <LI><i>{{title}}</i> by {{author}}</LI> {{/each}} </UL> </template>
客户端/bookList.js:
Template.bookList.rendered = function() { Session.setDefault('books', [ {title: "To Kill a Mockingbird", author: "Harper Lee"}, {title: "1984", author: "George Orwell"}, {title: "The Lord of the Rings", author: "JRR Tolkien"}, {title: "The Catcher in the Rye", author: "JD Salinger"}, {title: "The Great Gatsby", author: "F. Scott Fitzgerald"} ]); }; Template.bookList.helpers({ books: function () { return Session.get('books'); } });
服务器/startup.js:
Meteor.startup(function () { // code to run on server at startup }); ~~~ Check it out on MeteorPad: [Initial Code Cleanup](http://meteorpad.com/pad/MwvMcsBAzfbWwEXp3/Book-List:%20Initial%20Code%20Cleanup) Verify that everything's running correctly by checking the browser window and then we're good to move on to the next step. ## Using the Database in Meteor The Meteor server runs on top of a MongoDB database. In this section of our tutorial, we will move the static list of books out of the session variable and into that database. First, delete the Template.bookList.rendered code, so that we're no longer putting stuff into that session variable. Next, we should add that list of books to the database as fixture data when the server initializes. As you'd expect for MongoDB, Meteor stores data in "collections". So, we'll create a new collection for our books. To keep things simple we will name it "books". It turns out that both the client and the server will want to know about this collection, so we'll put this code in a new subfolder: "/lib/". Meteor knows automatically that files in "/lib/" run on the client and the server. We'll create a file called "lib/collections/books.js", and give it just one line of code: ~~~ js Books = new Meteor.Collection("books");
在指向 http://localhost:3000 的浏览器窗口中,转到开发人员控制台并检查“Books”的值。 它现在应该是一个 Mongo 集合! 尝试运行“Books.find().fetch()”,你会得到一个空数组——这是有道理的,因为我们还没有向其中添加任何书籍。 我们可以尝试在控制台中添加项目:

Books.insert({title: "To Kill a Mockingbird", author: "Harper Lee"})
在控制台中添加东西是相当乏味的。 相反,我们将进行设置,以便在服务器启动时自动将夹具数据插入数据库。 所以让我们回到“server/startup.js”,并添加:
Meteor.startup(function () { if (!Books.findOne()) { Books.insert({title: "To Kill a Mockingbird", author: "Harper Lee"}); Books.insert({title: "1984", author: "George Orwell"}); Books.insert({title: "The Lord of the Rings", author: "JRR Tolkien"}); Books.insert({title: "The Catcher in the Rye", author: "JD Salinger"}); Books.insert({title: "The Great Gatsby", author: "F. Scott Fitzgerald"}); } });
现在,当服务器启动时,如果没有数据,我们将添加夹具数据。 我们可以通过返回终端、停止流星服务器并运行以下命令来验证这一点:
meteor reset
注意:你很少需要这个命令,因为它会重置 - 即清除- Meteor 正在使用的数据库。 如果您的 Meteor 应用程序在数据库中有任何用户数据,则不应运行此命令。 但在这种情况下,我们将清除我们拥有的任何测试数据。
现在我们将再次启动服务器:
meteor
启动时,Meteor 会运行启动例程,看到数据库是空的,然后添加夹具数据。 此时,如果我们进入控制台并输入“Books.find().fetch()”,我们会得到之前的五本书。
这一步剩下的就是在屏幕上显示书籍。 幸运的是,这就像替换“return Session.get('books');”一样简单在“书籍”助手中使用以下内容:
return Books.find();
我们又开始营业了! 应用程序现在显示来自数据库游标的数据,而不是来自会话变量的数据。
在 MeteorPad 上查看:迁移到数据库
安全问题
我会先说:“不要这样做”。
如果有人在他们的浏览器中启动这个应用程序,进入控制台并输入“Books.remove({})”,你认为会发生什么?
答案是:他们会清除收藏品。
所以,这是一个相当大的安全问题——我们的用户对我们的数据库有太多的访问权限。 任何客户端都可以访问整个数据库。 不仅如此,任何客户端都可以对整个数据库进行任何更改,包括“.remove({})”数据擦除。
这不好,所以让我们修复它。
Meteor 使用所谓的“包”来添加功能。 什么是 Node.js 的模块,什么是 Ruby 的 gem,包是 Meteor 的捆绑功能。 有各种各样的东西的包。 要浏览可用的内容,请查看大气.js。
我们使用“meteor create”制作的默认流星应用程序包括两个名为“autopublish”和“insecure”的包。 第一个包使客户端可以自动访问整个数据库,第二个包使用户可以对该数据库执行任何操作。
让我们删除那些。 我们可以通过从 app 目录运行以下命令来做到这一点:
meteor remove autopublish insecure
完成后,您会看到图书列表数据从屏幕上消失(因为您不再有权访问它),如果您尝试调用“Books.insert”,您将收到错误消息:“插入失败: 拒绝访问”。 在 MeteorPad 上:安全性过大
如果您从本 Meteor 教程中没有其他内容,请记住这一点:当您部署 Meteor 应用程序时,请确保删除自动发布和不安全的包。 Meteor 有许多良好的安全预防措施,但如果您安装这两个软件包,所有这些都是徒劳的。
那么,如果 Meteor 存在安全隐患,为什么还要自动包含这些包呢? 原因是,特别是对于初学者来说,这两个软件包使上手更容易——您可以从浏览器的控制台轻松调试和调整数据库。 但尽快放弃自动发布和不安全是一个好习惯。
发布和订阅
所以我们修复了这个巨大的安全漏洞,但我们引入了两个问题。 首先,我们现在无法访问数据库。 其次,我们无法与数据库进行交互。
让我们在这里解决第一个问题。 Meteor 通过让服务器“发布”数据库的一个子集并让客户端“订阅”该发布来提供对数据库的安全访问。
首先,让我们创建“/server/publications.js”:
Meteor.publish('books', function() { return Books.find({}); });
我们将创建“/client/subscriptions.js”:
Meteor.subscribe('books');
在 MeteorPad 上查看:发布和订阅
服务器“发布”一个可以访问所有数据的游标,而客户端在另一端“订阅”它。 客户端使用此订阅来使用游标的所有数据填充数据库的镜像副本。 当我们访问“Books.find().fetch()”时,我们会看到所有五个对象,并且我们看到它们像以前一样显示在屏幕上。
现在的不同之处在于,限制客户端可以访问的内容真的很容易。 尝试将发布“find()”切换到数据的子集:
Meteor.publish('books', function() { return Books.find({}, {limit:3}); });
现在客户只看过五本书中的三本书,没有办法找到其余的。 这不仅对安全性有很大好处(我看不到其他人的银行账户),而且您可以使用它来打包数据并避免客户端超载。
添加新书
我们已经了解了如何以有限的、安全的方式为客户提供对数据库的读取访问权限。 现在,让我们看看第二个问题:我们如何让用户更改数据库而不让他们做任何他们想做的事情? 摆脱不安全的包使得客户端根本无法访问 - 让我们再次尝试允许添加书籍。 在meteor中,我们通过向服务器添加一个“方法”来做到这一点。 让我们添加一个方法,即添加一本新书到“/lib/collections/books.js”:
Meteor.methods({ addBook: function(bookData) { var bookID = Books.insert(bookData); return bookID; } });
如您所见,这接受了“bookData”——在本例中,这是一个带有“title”和“author”字段的对象——并将其添加到数据库中。 一旦您的客户端重新加载,我们就可以从客户端调用此方法。 您可以转到控制台并输入如下内容:
Meteor.call('addBook', {title: "A Tale of Two Cities", author: "Charles Dickens"})
快! 你在书单上得到另一本书。 使用控制台非常笨重,所以让我们继续在“bookList”模板的末尾添加一个简单的表单,让我们添加新书:
<form class="add-book"> Title:<br> <input type="text" name="title"> <br> Author:<br> <input type="text" name="author"> <input type="submit" value="Add Book"> </form>
我们可以使用事件案例将其连接到 JavaScript,就像我们在原始测试应用程序中所做的那样:
Template.bookList.events({ "submit .add-book": function(event) { event.preventDefault(); // this prevents built-in form submission Meteor.call('addBook', {title: event.target.title.value, author: event.target.author.value}) } });
您可以在 MeteorPad 上看到这一点:方法
当我们设置了不安全和自动发布时,客户端可以访问和更改整个数据库。 现在不安全和自动发布消失了,但是有了发布、订阅和方法,客户端可以访问数据库并以受控方式与之交互。
附带说明:您还可以通过使用“允许和拒绝规则”来解决 Meteor 中的安全问题。 您可以在discovermeteor.com 上找到更多关于这些以及我更喜欢上述方法的一些原因。
用户认证
看起来我们好像刚刚回到起点,但有一个重要的区别:我们现在很容易限制对数据库的访问。 要了解它是如何工作的,让我们尝试将用户添加到此应用程序。 我们将在我们的应用程序中添加一个登录系统,然后不再让所有客户端使用一个系统范围的书籍列表,而是让每个用户只能添加或阅读他们自己的书籍列表。
进入 app 目录,安装两个包:
meteor add accounts-ui accounts-password
那里。 您刚刚向应用程序添加了登录系统。 现在我们只需要将登录 UI 添加到 book-list.html。 将这一行放在正文的顶部:
{{> loginButtons}}
您应该会在屏幕顶部看到登录提示:
请注意,如果您单击登录链接,它会要求您输入电子邮件地址和密码。 我们可以通过创建包含以下内容的“/client/config.js”将其切换到简单的用户名/密码登录系统:
Accounts.ui.config({ passwordSignupFields: "USERNAME_ONLY" });
此时,您可以在控制台中输入“Meteor.userId()”,它会返回“null”。 您可以尝试单击该链接来创建一个帐户。 调用“Meteor.userId()”现在应该返回一个 ID 字符串。 服务器可以访问同一条信息(如“this.userId”),因此让“add books”方法强制用户登录并包含一个 userID 字段很简单:
Meteor.methods({ addBook: function(bookData) { if (this.userId) { bookData.userID = this.userId; var bookID = Books.insert(bookData); return bookID; } } });
现在剩下的就是限制客户端,只显示该用户添加的书籍。 我们利用出版物的能力来缩小客户可以访问的范围:
Meteor.publish('books', function() { return Books.find({userID: this.userId}); });
现在,该出版物仅查找来自该特定用户的书籍。 我们甚至可以将 Blaze 表达式中的 userId 访问为“{{currentUser}}”; 我们可以将它与“{{#if}}”指令一起使用(它完全按照您的想法执行),仅在用户登录时显示数据:
<template name="bookList"> {{#if currentUser}} <h3>Here are your books:</h3> <UL> {{#each books}} <LI><i>{{title}}</i> by {{author}}</LI> {{/each}} </UL> <form class="add-book"> Title:<br> <input type="text" name="title"> <br> Author:<br> <input type="text" name="author"> <input type="submit" value="Add Book"> </form> {{else}} <h3>Please log in to see your books</h3> {{/if}} </template>
在 MeteorPad 上查看最终结果:用户
部署
现在我们可以将这个 Meteor 应用程序部署到 Internet。 我们通过转到终端上的应用程序目录并执行以下操作来做到这一点:
meteor deploy <your app's name>.meteor.com
确保将“<your app's name>”替换为应用程序实例的实际短名称。 运行此命令将提示您使用 Meteor 设置一个帐户,然后它将您的新应用程序放在 Meteor 的测试服务器上,以便您可以在 Internet 上试用。
对于一个快速演示,这个meteor.com 解决方案就是您所需要的。 Meteor 团队尚未宣布对其服务器上的存储或带宽有任何明确的限制。 唯一值得注意的限制是,如果您的应用长时间未使用,则该网站需要几秒钟才能为下一个用户启动。
也就是说,meteor.com 不用于商业用途。 但是,当您投入生产时,有像 Modulus 和 Digital Ocean 这样的平台即服务公司可以轻松部署 Meteor 应用程序。 如果您想将流星应用程序部署到您自己的服务器,“meteor up”也使该过程变得简单。
下一步
恭喜! 在您学习 Meteor 的过程中,您现在已经制作并部署了一个非常简单的实时 Meteor Web 应用程序。 显然,这只是进入整个特性和功能领域的一小步。 如果您喜欢到目前为止所看到的内容,我强烈推荐 David Turnbull 的 Your First Meteor Application,它引导读者创建一个更复杂的应用程序,并在此过程中提供有关流星功能的更多信息。 它可以低价购买 Kindle 电子书,也可以在 Turnbull 的网站上以免费 PDF 的形式提供。
您还需要探索可用于 Meteor 的软件包。 十分之九,“我如何在 Meteor 中执行 <x>?”的答案。 是“有一个包”。 “如何向我的应用程序添加路由?” 您使用 Iron Router。 “如何提供 RESTful API?” 您使用 RESTivus。 “我如何包括单元测试?” 您使用速度。 “如何添加模式验证?” 您使用 Collection2。 查看所有可用的软件包,您很容易迷失在 Atmosphere.js 上。
为什么不使用流星?
正如您从本教程中看到的那样,Meteor 编写应用程序既简单又有趣,但如果我没有提到它的缺点,那就太失职了。
Meteor 还比较不成熟。 它在去年 10 月达到了 1.0,这导致了一些问题。 如果你想做一些晦涩的事情,可能还没有人为那个功能写过一个包。 确实存在的软件包更有可能存在错误,这仅仅是因为它们存在的时间还不够长,无法解决所有问题。
Meteor 的缩放也可能有些未知。 有很多 Meteor 网站可以扩展到合理数量的用户,但很少有非常大的网站——没有像 Facebook 或 LinkedIn 那样拥有数千万或数亿用户的网站。
然而,对于大多数应用程序来说,Meteor 是一个完美的选择,因为它是一个减少开发时间和参与伟大事物开始的机会。