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 是一個完美的選擇,因為它是一個減少開發時間和參與偉大事物開始的機會。