Skip to content

Instantly share code, notes, and snippets.

@xareelee
Last active June 9, 2023 17:05
Show Gist options
  • Save xareelee/a58c0d07b46152235ca4b32c9131be91 to your computer and use it in GitHub Desktop.
Save xareelee/a58c0d07b46152235ca4b32c9131be91 to your computer and use it in GitHub Desktop.
如何學習 Meteor.js (第一階段)

如何學習 Meteor.js (第一階段)

Meteor 是一個 full-stack 的開發框架 (或者該說是平台),它可以利用一種語言 (JavaScript) 來輕鬆完成前後端的開發。是目前前景看好的選擇。

本階段的目標是:

  • 了解 Meteor 是什麼樣的平台,基本架構,可以做到什麼樣的事情 (2.5 hr)
  • 能夠自己獨立完成一個 Meteor 的 ToDo App (不斷的練習一樣的 App,直到完全熟悉,+10 hr)

這階段大約花 10-20 hr,可以利用一個週末的兩天時間,或是一個禮拜的晚上來完成。

在開始之前,你應該要有 JS/HTML/CSS 的基本知識;最好也懂 React。你可以在 "A Study Plan To Cure JavaScript Fatigue" 找到學習路徑 (或是看中文翻譯)。

Meteor 基本知識

先看以下影片,吸收一下 Meteor 的知識以及如何開發

以上看完就可以開始以下的學習 (學完之後記得再回來看一次這兩個影片,可以注意到一些細節)。

開始學習開發

學習教材

我們利用官方提供的 tutorial 來學習。初學者第一個學習教材,就是官方 Tutorial 中的 ToDo App。在本教學的最終目標,是要能夠完全不用看文件,靠自己寫出一個跟官方一樣的 ToDo App。這個過程必須靠不斷的練習,直到將短期記憶變成長期記憶,並且熟悉整個開發流程。

官方 ToDo App 根據不同的前端技術,分成三種教學:BlazeAngularReact。如果你沒有特定的偏好,建議選擇 React,畢竟是目前的主流,而且 Meteor 官方也有打算將 React 變成開發的主要框架。

學習方法

  1. 先按照官方教學,把 ToDo App 做一次,了解每一步驟,並試著記住。

  2. 立刻閱讀下面整理的「開發提示」,並立刻按第三步開始練習自己實作,幫助記憶開發方式和架構。

  3. 練習自己開發 ToDo app (每次都重頭開始)

    • 先靠自己的學到的,試著自己動手寫出一樣的 App。
    • 忘記要做什麼,可以看本篇提供的「開發提示」(只有任務提示,但沒有關鍵代碼)。
    • 不清楚 API 的話,試著查找官方 API (而不是查找官方的 ToDo 的 sample code 喔)。
    • 遇到錯誤,先試著自己找原因、除錯。
    • 真的不知道要如何進行的話,或是找不到問題,才看官方文件的代碼 (盡可能不要先看)。
    • 做完後,核對一次代碼跟官方的有哪些不同,記錄下來,然後立刻複習、記憶。
    // 每次的練習模式
    靠自己思考怎麼做
        -> 忘記則看本篇開發提示
        -> 試著查官方 API、自己除錯 
        -> 真的不行再看官方 ToDo 教學的代碼
    
  4. 迭代練習:可以參考下一節的「學習日程表」,重簡單的章節重複練習,再慢慢增加

    • 前幾次練習時如果忘記,可以先不查官方 API,直接看官方教學代碼
    • 增加實作章節時,前面章節不可省略,都是重頭開始練習。
    • 除非你對該章節已經可以完全不看文件即可完成 3 次,才可以跳過該章節不練習。
    // 迭代程度
    依靠本篇的開發提示,儘量自己寫,忘記時參考官方教學
        -> 依靠本篇的開發提示,重頭開發,忘記時查官方 API、google,儘量不靠官方教學
        -> 重頭開發,但儘量不靠本篇的開發提示,
        -> 可以不用看任何文件和提示就能寫出一個 ToDo App (合格標準)
    
  5. 一直練習直到「不用看任何文件和提示就能寫出一個 ToDo App」。可以分天練習,但是應該要每天練習。中間一旦空了幾天,很快就會忘記。通常轉換成長期記憶需要 7 天以上的持續練習。

從簡單到全部的練習,共分七個階段。每個階段練習 1-3 次,直到有把握進入下一個階段。你可以分七天完成。你會越做越快、越做越熟悉,直到完全記住、了解。

  | 練習章節 | 開發提示 | 查API | 官方教學 | 方針

------|---------|-------------|---------|------|----- level 1 | Ch 1-5 | Ch 1-5 | 不用查 | Ch 1-5 | 快速模仿、加深印象 level 2 | Ch 1-5 | Ch 1-5 | Ch 1-5 | 不可看 | 不靠官方教學,實作出 1-5 的功能 level 3 | Ch 1-8 | Ch 1-8 | Ch 1-5 | Ch 7-8 | 加入 Ch 7-8 level 4 | Ch 1-8 | Ch 7-8 | Ch 7-8 | 不可看 | 不靠官方教學,實作出 1-7 的功能 level 5 | Ch 1-10 | Ch 7-10 | Ch 7-8 | Ch 9-10 | 加入 Ch 9-10 level 6 | Ch 1-10 | Ch 9-10 | Ch 7-10 | 不可看 | 不靠官方教學,實作出 1-10 的功能 level 7 | 全部 | 不可看 | 不可看 | 不可看 | 不看任何文件就可以寫出 ToDo App

"Practice, practice and practice."

"Practice makes perfect."

從練習中學到了什麼?

在這個練習中,你會發現寫 Meteor ToDo App 就像是在寫 Whatsapp 這樣的聊天軟體。如果你有試著用兩個不同的瀏覽器登入不同的帳號,分別新增不同的 task,那畫面看起來就像是在聊天一樣。而且你還能更新刪除一個聊天內容 (並且在有權限底下)。開發一個聊天室的原型就是這麼容易 (只需要 1-2 小時)!但你還需要更了解 Meteor 的項目組成和管理,才能開發更大、更複雜的應用。

在練習的一開始,你會理解如何組成一個的頁面。Meteor 會在後端把你的代碼組成一個 App 後,再傳給客戶端。了解 Meteor 如何打包你的 Single Page Application 是下一階段的課題。

另外就是 Meteor 一開始就加入了 insecure/autopublish 的功能,方便開發客戶端;但是當要上線的時候,要記得拿掉這些功能,改用 Meteor 的 methods/call/publish/subscribe 來存取資料庫。理解 Meteor DDP 的資料同步機制,是學習 Meteor 重要的課題。

Meteor 提供了良好的帳戶管理,並提供套件,可以幫助你快速打造產品原型。你需要了解更多的 API 和支援套件,來幫助你快速完成各種任務。

在整個練習中,你了解知道如何拆分 front-end/back-end 的代碼。整個練習當中你可能會產生一些疑惑:「我沒有傳 userId 給後台,那後端怎麼知道 currentUser 是誰?」、「有 DDP 同步資料庫,那我是否還需要 RESTful API?」、「如何 Routing?如何呈現其他 URL link 的頁面?」。這些都是接下來的學習議題。

下一步呢?

然後,你可以加入以下變化再次練習:

  • 改用 TypeScript 開發一次
  • 進行 test-driven development (TDD)
  • 試著使用自己的 CSS 開發
  • 加入一些有趣的功能

最後,進入下一個學習階段 (第二階段,待續)

本開發提示,是使用 React 前端框架的提示。歡迎留言補充。

注意:每章的實作順序不見得和教學一樣,但目標和最終結果是一樣的。下面提示中的開頭,顯示代碼可以在官方文件的哪個部分找到。

建立初始的 meteor app。

  • 建立一個新的 meteor app 項目、執行、並顯示初始頁面在瀏覽器。

加入 React 顯示一個靜態資料的 ToDo app。

  • (2.1) 安裝 React 開發需要的套件
  • (2.2/2.3) 將 client/main.htmlclient/main.jsx 修改成為
  • (2.4) 加入 imports/ui/App.jsx,顯示一個 h1 的標題和 ul 的 table
  • (2.5) 加入 imports/ui/Task.jsx 將每個 task 轉換成 li
  • 先使用靜態資料顯示幾筆 tasks (後面會改為動態)
  • (2.6) 可以拷貝、加入官方的 CSS (client/main.css),這部份不練習重點。但要自己寫也可以。

注意:

  • 加入需要的 React.PropTypes 檢查 (後面不再提示)

加入 MongoDB 來顯示一個動態資料的 ToDo app。

  • (3.1) 需要一個 imports/api/tasks.js 來建立一個共用的 Mongo.Collection 物件,來操作 tasks
  • (3.4) 建立幾筆 Mongo.Collection 的資料,並讓 App 可以顯示 Mongo.Collection 的資料
  • (3.4) 為了要讓 React Component 可以在 Mongo.Collection 發生改變時,能夠呼叫 callback、產生新的 props、傳給 App 然後重新繪製,需要使用 createContainer() 來重新包裝 App (這邊需要安裝額外的插件)

注意:

  • (3.2) server/client 都需要 import 和執行 imports/api/tasks.js,才能讓資料在前後台順利存取

使用者可以在頁面新增 task。

  • (4.1) 在 header 顯示一個 input field,讓使用者可以輸入新的 task,並呼叫 callback
  • (4.2) 在 callback 中取得該輸入的文字,然後加到 MongoDB 中 (畫面應該會跟著顯示新的資料)
  • (4.3) 試著依照時間戳反向排序 (越新的越靠上)

注意:

  • MongoDB 的 find() 排序,需要使用 { sort: { ... } }
  • 範例中沒有過濾掉空字串,實際上應該要先檢查輸入字串是不是空字串,才能加入 Database。

使用者可以在頁面刪除或更新 task 完成狀態。

  • (5.1) 在 Task component 內加入一個 delete button (className='delete') 和 complete checkbox,並在使用者點擊時,成功呼叫 callback
  • (5.1) 完成 delete 時,從 MongoDB 刪除該 task 的任務
  • (5.1) 完成 complete 時,改變 MongoDB 該 task 的狀態
  • (5.1) Task 依照是否為 complete 來改變顯示狀態 (利用 className='checked'/CSS 來完成);task.text 使用 <span className="text">...</span> 來顯示

注意:

  • MongoDB 的 update() 需要使用 { $set: { ... } } 來改變狀態

本章節跳過

利用 state 來記錄使用者操作狀態,並控制是否只顯示「未完成 tasks」。

  • (7.1) 在 header 加入一個 checkbox,提示使用者可以點擊它來過濾掉已完成 tasks。使用 className="hide-completed" 的 label 來包住該控制鍵。
  • (7.2/7.3/7.4) 該 checkbox 可以在點擊時成功呼叫一個 callback 來觸發狀態改變,來決定是否只顯示未完成 tasks。
  • (7.5/7.6) 加入一個 incompleteCount 的功能,在標題顯示未完成 tasks 的數量

思路:

  • 改變顯示狀態而非改變資料記錄時,使用 setState 而不是更改 MongoDB 的資料狀態。state 永遠只存放「運行時」的資料。
  • 實踐「過濾」功能:應該在 App 內判斷 data source 是否需要過濾;而不是讓 Task 依照 task 狀態來決定是否要回傳一個 <li> 還是回傳空字串 (雖然顯示結果一樣,但要思考代碼封裝原則)。
  • 未完成數量的計算,應該利用 MongoDB.find({ checked: { $ne: true } }).count()createContainer () 內提供資料;而不要在 render 時再根據 tasks 做篩選和計算。

讓使用者能夠登入,並只有在登入時才能新增 task;task 上應該要顯示 username。

你可能會需要查 Blaze/Template,以及 Accounts.ui.config 的 API

  • (8.1) 使用 Meteor 提供的 Accounts UI (Blaze template) 來快速完成,需要額外安裝相關 meteor package (accounts-ui/accounts-password)
  • (8.2/8.3) 將 Blaze template (loginButton) 轉換成 React Component,並在 <App/> 中使用。應該要能出現 login 的按鈕
  • (8.4/8.5) 加入 imports/startup/accounts-config.js,依照 Accounts.ui.config API 文件 設置只使用 username/password 即可註冊和登入 (無需 email)。
  • 檢查點:可以成功註冊、登入、登出。用另外一個瀏覽器開啟 ToDo App,並註冊和登入另外一個使用者帳號 (之後可以模擬兩個使用者的交互)。
  • (8.6/8.7/8.9) 在新增 task 時,記錄顯示是由目前哪個 user 建立的。
  • 雖然等下會改成「只有登入時,才能新增 task」,但顯示上也要考慮向前相容 (無使用者的過去任務)。目前還是能由匿名者是有介面可以輸入,但是在 insert 時會報錯,可以考慮額外加上警告窗提示。
  • (8.8) 把新任務輸入欄位隱藏,只有在使用者登入的狀況才顯示。

注意:

  • Accounts.ui.config() 需要在 client 端一開始被執行一次才會有用 (繪製 Accounts UI 前)
  • 有 public API Meteor.userId()/Meteor.user() 可以在 server/client 都取得使用者資料。但是要去了解:「server 端怎麼知道 Meteor.user() 是哪個 user?」
  • 本章節有許多地方需要深入搞懂,建議多花點時間理解 API 和代碼

Meteor 建立時加入許多便利於開發的套件,我們需要移除 insecure 來避免 client 端直接操作資料庫,而改用 Meteor.methods()/Meteor.call() 來更新資料庫,並給予 server side 能夠有機會判斷數據變更請求是否合法,來拒絕資料庫的變更。

  • (9.1) 移除 insecure 套件。
  • 檢查點:移除後,會發現前端 App 無法正常的 insert/update/remove,console 會出現 failed: Access denied 的提示。
  • (9.2/9.3/9,4) 將 client 端的資料庫操作 (insert/update/remove) 改用 Meteor.call(),並實現對應的 Meteor.methods()

注意:

  • 記住在 Meteor.methods() 中,加入 check 來檢查 params 的型別;使用 throw new Meteor.Error() 來發佈異常狀態 (例如是否為合法的使用者操作)。
  • 實現的 Meteor.methods() 應該要在前後端都有被執行過,這樣才能生效。通常直接放在 collection 的檔案內 (本案例為 imports/api/tasks.js)
  • 移除 insecure 只會阻止變更資料庫的方法 (insert/update/remove),而不會阻止能否撈取資料 find();要限制 client 端的資料撈取,要實作下一章的 publish/subscribe
  • 報錯使用 Meteor.Error() 而非 Error()

Meteor 建立時加入許多便利於開發的套件,我們需要移除 autopublish 來避免 client 端直接取得全部資料庫,而改用 Meteor.publish()/Meteor.subscribe() 來限制 client 端能取得的資料。

我們將實作 private 的功能,讓使用者只讓自己建立的 task 變成私有的 (只有自己能看見),因此後台在給予資料時,或過濾掉非該使用者的 private task (也就是看不到別人的 private tasks,但看得到別人的 public tasks)。

  • (10.1) 移除 autopublish
  • (10.2) server 端實作 Meteor.publish() 功能,限制可以給予的資料。此時 App 應該無法得到任何資料。
  • (10.3) client 端實作 Meteor.subscribe(),此時 App 可以在沒有 autopublish 時獲得資料。
  • (10.4/10.7/10.8) 類似於實作 checked 的功能。在 Task 介面上設立一個按鈕,顯示 private/public 的狀態,點擊時可以改變。但必須驗證 userId,只有編輯該 task 的 user 和原始 task 創建者一致的時候,才能,否則報錯。這時使用者應該不能修改其他使用者的 task 的 private/public。buttonclassName="toggle-private"
  • (10.9/10.10) 目前點擊 private/publich 按鈕時,顯示也會跟著改變;透過 classnames 套件,在需要顯示為 private 時,多傳一個 className="private"Task<li /> 內。
  • **檢查點:**這個時候應該是可以修改別人的 task 成為 private (可以開兩個視窗登入不同帳號測試),而且不同的帳戶資料應該是連動的 (一個 user 修改的狀態,另外一個 user 可以馬上看到)。
  • (10.12) 先實作一個保護邏輯:就是當該 task 為私有時,在資料處理時,只允許該 task 的 onwer 可以修改 private 的狀態 (包括修改 checked、private 或 delete);否則報錯。
  • (10.5/10.6/10.7) 前端操作保護:只有自己的 task 才顯示 private/public 按鈕。使用者沒有介面可以更改別人 task 的 private/public。
  • 檢查點:此時還是能看到別人的 private tasks,但是你沒有介面更改它的狀態,也不能刪除完成它 (前面有實作資料庫保護判斷)。
  • (10.11) 後台篩選:依照 userId,透過 Meteor.publish() 過濾掉其他使用者的 private 訊息。現在使用者看不到別人的 private tasks,因為後台 DB 傳送的資料已經篩選過了。

注意:

  • Mongo 的 find() 可以多次串連 (例如 Tasks.find(...).find(...).find(...)),這樣可以在 server 先 find() 限縮合法資料,再由 client 使用 find() 來找尋有用資料。最後使用 fetch() 才會取得找到資料的 json array

  • 前後端都應該針對 private 進行實作,不可以只做一端,以免發生問題:前端要限制編輯別人資料的操作方式;後端要驗證資料修改者是否合法,並過濾掉其他人私有資料給前端使用者。

  • 後端篩選資訊:透過 find({ $or: [...] }) 來加入過濾邏輯 (只允許公開的,或是該使用者的)

  • Meteor.user()/Meteor.userId() 只能在 Meteor.methods() 內使用,其他地方要使用 this.userId

  • 承上,由於用到了 this,這個關鍵字在 JS function 和 ES6 block 是不同的,因此在 Meteor.publish() 內篩選資料時,會導致難以發現的錯誤,因此使用上要小心:

    // (正確用法) JS function: `function() {}` 
    Meteor.publish('Tasks', function() {
      console.log(`user id: ${this.userId}`);  // ntHS8jp57pwjLtiSi
      ...
    });
    // (錯誤用法) ES6 block: `() => {}`
    Meteor.publish('Tasks', () => {
      console.log(`user id: ${this.userId}`);  // undefined
      ...
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment