- 后端
- 前端
- 开发工具
- 移动开发
【免费下载链接】meteor
Meteor, the JavaScript App Platform
本文是 Meteor 3 + Svelte 实战教程的第二步,围绕"将静态任务列表升级为真实数据库驱动的 Todo 应用"这一核心目标,逐步讲解如何创建tasksMongo 集合、在服务端启动时初始化种子数据,并通过"发布(Publish)/订阅(Subscribe)"机制把数据同步到 Svelte 客户端进行响应式渲染。读完本章,你将掌握Mongo.Collection的创建与导入规范、Meteor 3 的*Async异步 API 用法、Meteor.startup初始化数据库的技巧,以及用Tracker.autorun在 Svelte 组件中订阅集合并触发重渲染的完整链路。
为什么需要集合(Collections)
Meteor 已经为你内置好了 MongoDB——你不需要手动安装、启动或配置数据库服务。但要真正把数据存下来,我们需要先创建"集合(Collection)",它是存放"文档(Document)"的地方。在本项目中,我们要存放的文档就是我们的tasks(任务)。
集合是 Meteor 数据层的核心抽象。当你在创建集合时传入一个name字符串,你就声明了一个持久化集合:它存储在服务端的 MongoDB 中,对所有用户可见,客户端和服务端可以用完全相同的 API 访问它。根据 v3-docs/docs/api/collections.md 的说明:
- 在服务端(不指定
connection时),同名集合会创建在底层的 Mongo 服务器上,你对集合调用的方法会(在通过访问控制规则检查后)直接翻译为普通的 Mongo 操作; - 在客户端,会创建一个 Minimongo 实例——它是用纯 JavaScript 实现的内存版 Mongo,作为本地缓存存放当前客户端正在使用的那部分数据,
find()查询直接命中缓存,无需与服务器通信; - 当你在客户端写入数据库时(
insert/update/remove),命令会先在本地立即执行,同时通过 stubs 机制发送到服务端执行。
这一"本地即时生效 + 服务端同步"的设计,正是 Meteor 免写 REST 接口的底气所在,也是本章后续发布订阅机制的基础。
2.1:创建 Tasks 集合
创建一个新文件imports/api/TasksCollection.js,它实例化一个新的 Mongo 集合并导出它:
import { Mongo } from "meteor/mongo"; export const TasksCollection = new Mongo.Collection("tasks");这里做了两件关键的事情:
- 从
meteor/mongo包导入Mongo命名空间,其中包含Mongo.Collection构造器; - 用名称
"tasks"创建一个持久化集合并导出。
注意我们将文件放在了imports/api目录下——这是存放 API 相关代码(如 publications 发布、methods 方法)的约定位置。目录名完全取决于个人偏好,但遵循这一惯例能让项目结构更清晰、更易维护。
如果启动项目自带了
imports/api/links.js文件,现在可以删掉它,因为本教程用不到。关于应用结构以及 import/export 的更多说明,可参考项目中的模块文档(如 v3-docs/docs/api/collections.md 与 v3-docs/docs/packages/modules.md)。
从源码看Mongo.Collection的底层行为
在 packages/mongo/mongo_common.js 与 packages/mongo/collection/methods_async.js 中可以印证这一行为:传入name时,服务端将集合落到后端 Mongo 驱动上,客户端则建立 Minimongo 缓存实例;不传name(传null)时创建的是本地集合——不同步到任何地方,只是一个支持 Mongo 风格find/insert/update/remove的本地草稿本,客户端与服务端都用 Minimongo 实现。
Meteor 3 之后,同步写方法在服务端会直接抛错。因此本教程中统一使用*Async异步版本,例如insertAsync、find().countAsync(),这是 Meteor 3.x 及以后的强制约定(详见 v3-docs/docs/api/collections.md 中的 danger 提示)。
2.2:初始化 Tasks 集合(服务端种子数据)
集合要真正工作,需要在服务端导入它,以便 Meteor 为其建立所需的底层管道。你可以用以下两种方式之一导入:
import "/imports/api/TasksCollection"; // 仅导入副作用 import { TasksCollection } from "/imports/api/TasksCollection"; // 导入并在本文件使用如果要在同一个文件中使用它,就采用第二种写法;无论如何,确保它被导入了。
导入之后,判断集合中是否有数据变得非常简单;如果没有,我们就可以很方便地插入一些示例数据。server/main.js中原来的内容不再需要,替换为:
import { Meteor } from "meteor/meteor"; import { TasksCollection } from "/imports/api/TasksCollection"; const insertTask = async (taskText) => await TasksCollection.insertAsync({ text: taskText }); Meteor.startup(async () => { if (await TasksCollection.find().countAsync() === 0) { [ "First Task", "Second Task", "Third Task", "Fourth Task", "Fifth Task", "Sixth Task", "Seventh Task", ].forEach(insertTask); } });这段代码做的事情是:导入TasksCollection,然后遍历一个字符串数组,对每个字符串调用一个插入函数,把该字符串作为task文档的text字段写入集合。
值得注意的几个细节:
Meteor.startup:保证这段代码在 Meteor 服务端就绪后才运行。根据 v3-docs/docs/api/meteor.md,Meteor.startup中的代码总是在所有应用代码加载完成后执行,是初始化数据库的推荐位置。- 幂等初始化:
countAsync() === 0检查确保只在集合为空时插入种子数据,避免应用重启(每次代码保存都会触发自动重启)后重复插入七条重复任务。这是开发阶段最常用的"空库填充"模式。 insertAsync的异步契约:在 packages/mongo/collection/methods_async.js 中可以看到,insertAsync(doc)最终调用_insertAsync(doc, options):它会浅拷贝文档、在缺少_id时用_makeNewID()生成 ID、校验_id必须是非空字符串或Mongo.ObjectID,然后返回一个 Promise,resolve 出文档的唯一_id。服务端本地集合则直接下沉到 Mongo 驱动执行(this._collection.insertAsync(doc))。
2.3:用 Svelte 渲染 Tasks 集合
接下来进入最有趣的部分:用 Svelte 渲染集合数据。这非常简单。
在App.svelte文件中,导入TasksCollection,不再返回静态数组,而是返回数据库里保存的任务:
<script> import { Meteor } from "meteor/meteor"; import { Tracker } from "meteor/tracker"; import { onMount, onDestroy } from "svelte"; import { TasksCollection } from "../api/TasksCollection"; // Reactive state let handle; let subIsReady = false; let tasks = []; let computation; onMount(() => { handle = Meteor.subscribe("tasks"); computation = Tracker.autorun(() => { subIsReady = handle.ready(); tasks = TasksCollection.find().fetch(); }); return () => { computation?.stop?.(); handle?.stop?.(); }; }); onDestroy(() => { computation?.stop?.(); handle?.stop?.(); }); </script> <div class="container"> <header> <h1>Todo List</h1> </header> <ul> {#if subIsReady} {#each tasks as task (task._id)} <li>{task.text}</li> {/each} {:else} <div>Loading ...</div> {/if} </ul> </div>这段组件代码中包含了 Meteor + Svelte 集成最关键的四块拼图:
Meteor.subscribe("tasks"):在组件挂载(onMount)时向服务端发起订阅,返回值handle用于跟踪订阅状态(handle.ready())并在组件卸载时停止订阅;Tracker.autorun:注册一个响应式计算。每次handle.ready()状态变化或TasksCollection.find().fetch()的查询结果变化时,它都会重新执行,把最新数据写入tasks变量,从而触发 Svelte 的 UI 更新。这正是 Svelte 的声明式响应式与 Meteor 的 Tracker 响应式系统对接的桥梁;onDestroy/ onMount 返回值中的清理函数:通过computation?.stop?.()和handle?.stop?.()停止响应式计算与订阅,防止内存泄漏与重复订阅;{#if subIsReady}与{:else}:订阅未就绪时显示 "Loading ...",就绪后使用{#each tasks as task (task._id)}遍历渲染,key 使用task._id以保证列表 diff 的高效与稳定。
等一下!有个东西缺失了。如果现在运行应用,你会发现任务一个都没有渲染出来。
这是因为我们还需要把数据发布给客户端。
发布与订阅:把 MongoDB 同步到客户端
Meteor 不需要 REST 调用。它依赖的是把服务端的 MongoDB 与客户端的 MiniMongoDB 同步:首先在服务端发布集合,然后在客户端订阅它们,数据便沿着 DDP 协议自动流向客户端。
关于 Publications/Subscriptions 的更多信息,可参考 v3-docs/docs/api/meteor.md 中的 Publish and subscribe 章节。
第一步:创建发布(Publication)
为我们的任务创建一个发布:
import { Meteor } from "meteor/meteor"; import { TasksCollection } from "./TasksCollection"; Meteor.publish("tasks", function () { return TasksCollection.find(); });发布函数返回一个Collection.Cursor,Meteor 会把该游标命中的文档发布给每个订阅它的客户端。你也可以返回一个游标数组(但多个游标目前必须来自不同集合),或者使用底层的added/changed/removed低层接口手动控制发布集合——当不返回游标时必须调用ready()通知客户端初始记录集已完成。
从 packages/ddp-server/livedata_server.js 的源码可以看到,服务端为每个订阅调用publish_handlers中注册的发布函数,并将结果交给_publishCursor开始观察游标、把数据通过 DDP 推送给客户端;发布还支持多种publicationStrategies(如SERVER_MERGE默认策略)来决定多条发布数据的合并方式。这里返回TasksCollection.find()意味着把整个 tasks 集合发布给所有订阅者——对于 Todo 教程完全够用,但在真实项目中你通常会按this.userId或参数过滤,只发布当前用户的数据。
第二步:在服务端导入发布文件
现在,把这个发布文件导入到服务端:
... import { TasksCollection } from '/imports/api/TasksCollection'; import "../imports/api/TasksPublications"; // [!code highlight] const insertTask = taskText => TasksCollection.insertAsync({ text: taskText }); ...第三步:订阅发布(已在组件中完成)
剩下的唯一一步就是订阅这个发布——我们在上面的App.svelte中已经用 Tracker 和生命周期钩子完成了:
... onMount(() => { handle = Meteor.subscribe("tasks"); computation = Tracker.autorun(() => { // Reactive code here }); }); ...完整数据流回顾
至此,整个链路已经闭合:
- 服务端启动时(
Meteor.startup),tasks集合被播种了 7 条示例数据; TasksPublications.js注册了名为"tasks"的发布,把整个集合的游标暴露给客户端;- Svelte 组件挂载时调用
Meteor.subscribe("tasks"),数据通过 DDP 协议从服务端 MongoDB 同步到客户端的 Minimongo 缓存; Tracker.autorun感知到订阅就绪与查询结果变化,把TasksCollection.find().fetch()的结果写入 Svelte 响应式变量tasks;- Svelte 响应式渲染
<li>列表。
现在你的应用应该长这样了:
这一切是响应式的:你可以直接在服务端的 MongoDB 中修改数据,你的应用会立即做出反应并重新渲染,无需刷新页面、无需手写任何同步代码。
连接 MongoDB:查看与操作你的数据
你可以在应用目录下的终端中运行meteor mongo直接连接 MongoDB,也可以使用 Mongo 图形化客户端(如 NoSQLBooster)进行连接。Meteor 内置的 MongoDB 运行在3001端口。
首先,在 NoSQLBooster 等客户端中新建连接,服务器地址填localhost,端口填3001:
连接成功后,你会看到名为meteor的数据库,其下包含links(若未删除)、tasks、users等集合:
双击tasks集合即可查看其中存储的文档,每个文档都包含_id与text两个字段:
关于连接参数的补充说明:MONGO_URL环境变量用于指定 MongoDB 服务端地址(格式如mongodb://user:password@myserver.com:10139),MONGO_OPLOG_URL则用于配置 oplog 观察驱动以提升实时同步效率(详见 v3-docs/docs/cli/environment-variables.md)。本教程场景下使用 Meteor 内置的嵌入式 MongoDB,默认监听3001端口,无需额外配置即可连接。
小结与下一步
本章完成了从"静态假数据"到"数据库驱动"的关键跨越:
| 知识点 | 核心内容 |
|---|---|
| 创建集合 | new Mongo.Collection("tasks")并放在imports/api导出 |
| 服务端初始化 | Meteor.startup+countAsync() === 0幂等播种种子数据 |
| 异步 API | Meteor 3 服务端强制使用insertAsync/countAsync等*Async方法 |
| 发布 | Meteor.publish("tasks", () => TasksCollection.find()) |
| 订阅 | SvelteonMount中Meteor.subscribe("tasks")+Tracker.autorun响应式取数 |
| 响应式渲染 | 数据变化自动触发 Svelte 重渲染,客户端缓存 Minimongo 与服务端 MongoDB 实时同步 |
| 数据可视化 | meteor mongo或 NoSQLBooster 连接localhost:3001查看数据库 |
在下一步中,我们将使用表单来创建任务,把"添加任务"的能力真正交到用户手里。
- 后端
- 前端
- 开发工具
- 移动开发
【免费下载链接】meteor
Meteor, the JavaScript App Platform
相关推荐
VeChainThor节点性能优化指南:如何用Thor客户端提升区块链同步速度
VeChainThor节点性能优化指南:如何用Thor客户端提升区块链同步速度 作为VeChainThor区块链生态系统的官方Go客户端,Thor客户端是运行完
后端前端开发工具移动开发Meteor server-render 包:在 Meteor 应用中实现通用服务端渲染(SSR)
Meteor server render 包:在 Meteor 应用中实现通用服务端渲染(SSR) 导读 packages/server render 是 Me
后端前端开发工具移动开发Font Awesome与Sapper集成:Svelte的服务端渲染框架
Font Awesome与Sapper集成:Svelte的服务端渲染框架 引言 在现代Web开发中,图标库是提升用户界面体验的重要组成部分。Font Aweso
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考