meteor-fullstack

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Meteor Full-Stack Development (v3.x + React)

Meteor全栈开发(v3.x + React)

Modern Meteor 3.x (and 3.5+) full-stack development guide covering async-first patterns, React integration, MongoDB collections, methods, pub/sub, and project architecture.
Meteor 3 removed Fibers entirely — all server-side I/O is standard async/await. This is the single most important thing to internalize: every collection operation, every method body, every publication setup function that touches the database must be async. Node 24 is the standard runtime starting in Meteor 3.5.

现代Meteor 3.x(及3.5+)全栈开发指南,涵盖异步优先模式、React集成、MongoDB集合、方法、发布/订阅(pub/sub)以及项目架构。
Meteor 3彻底移除了Fibers——所有服务器端I/O均采用标准的async/await。这是需要掌握的最重要知识点:所有涉及数据库的集合操作、方法体、发布设置函数都必须是异步的。从Meteor 3.5开始,Node 24成为标准运行时。

Quick Reference: Async Collection APIs

快速参考:异步集合API

Always use the
*Async
variants on the server. Sync versions exist only for client-side Minimongo.
OperationAsync API (server)Sync API (client Minimongo only)
Insert
insertAsync(doc)
insert(doc)
Find one
findOneAsync(selector)
findOne(selector)
Update
updateAsync(selector, modifier)
update(selector, modifier)
Upsert
upsertAsync(selector, modifier)
upsert(selector, modifier)
Remove
removeAsync(selector)
remove(selector)
Count
countAsync()
(on cursor)
count()
Fetch
fetchAsync()
(on cursor)
fetch()
forEach
forEachAsync(fn)
(on cursor)
forEach(fn)
map
mapAsync(fn)
(on cursor)
map(fn)
Observe
observeAsync(callbacks)
observe(callbacks)
Create index
createIndexAsync(index, options)
Send email
Email.sendAsync(options)
Email.send(options)
Publications still return sync cursors via
collection.find(...)
for live-query reactivity — that hasn't changed.

在服务器端请始终使用
*Async
变体。同步版本仅适用于客户端Minimongo。
操作异步API(服务器端)同步API(仅客户端Minimongo)
插入
insertAsync(doc)
insert(doc)
查询单个文档
findOneAsync(selector)
findOne(selector)
更新
updateAsync(selector, modifier)
update(selector, modifier)
插入或更新
upsertAsync(selector, modifier)
upsert(selector, modifier)
删除
removeAsync(selector)
remove(selector)
计数
countAsync()
(游标调用)
count()
获取结果
fetchAsync()
(游标调用)
fetch()
遍历
forEachAsync(fn)
(游标调用)
forEach(fn)
映射
mapAsync(fn)
(游标调用)
map(fn)
监听
observeAsync(callbacks)
observe(callbacks)
创建索引
createIndexAsync(index, options)
发送邮件
Email.sendAsync(options)
Email.send(options)
发布功能仍通过
collection.find(...)
返回同步游标以实现实时查询响应性——这一点没有变化。

Core Concepts at a Glance

核心概念概览

Methods (RPC)

方法(RPC)

Server functions callable from the client. In v3, always async:
js
Meteor.methods({
  async 'todos.create'(text) {
    if (!this.userId) throw new Meteor.Error('not-authorized');
    return await Todos.insertAsync({ text, createdAt: new Date(), userId: this.userId });
  },
});

// Client
const id = await Meteor.callAsync('todos.create', 'Buy milk');
Read
references/methods-rpc.md
for stubs, optimistic UI,
applyAsync
options, and the simulation timing trap.
客户端可调用的服务器函数。在v3版本中,必须始终使用异步写法:
js
Meteor.methods({
  async 'todos.create'(text) {
    if (!this.userId) throw new Meteor.Error('not-authorized');
    return await Todos.insertAsync({ text, createdAt: new Date(), userId: this.userId });
  },
});

// 客户端
const id = await Meteor.callAsync('todos.create', 'Buy milk');
如需了解存根、乐观UI、
applyAsync
选项以及模拟时序陷阱,请阅读
references/methods-rpc.md

Publications & Subscriptions

发布与订阅

Server pushes reactive data to the client over DDP:
js
// Server
Meteor.publish('todos.byUser', function () {
  if (!this.userId) return this.ready();
  return Todos.find({ userId: this.userId });
});

// Client (React)
const { todos, isLoading } = useTracker(() => {
  const handle = Meteor.subscribe('todos.byUser');
  return {
    isLoading: !handle.ready(),
    todos: Todos.find({}, { sort: { createdAt: -1 } }).fetch(),
  };
}, []);
Read
references/pubsub.md
for composite publications, SubsManager caching, counts, publication wrappers, and MongoDB Change Streams configuration.
服务器通过DDP向客户端推送响应式数据:
js
// 服务器
Meteor.publish('todos.byUser', function () {
  if (!this.userId) return this.ready();
  return Todos.find({ userId: this.userId });
});

// 客户端(React)
const { todos, isLoading } = useTracker(() => {
  const handle = Meteor.subscribe('todos.byUser');
  return {
    isLoading: !handle.ready(),
    todos: Todos.find({}, { sort: { createdAt: -1 } }).fetch(),
  };
}, []);
如需了解复合发布、SubsManager缓存、计数、发布包装器以及MongoDB变更流配置,请阅读
references/pubsub.md

REST APIs (accounts-express)

REST API(accounts-express)

Build authenticated REST endpoints seamlessly using Express and
accounts-express
:
js
import express from 'express';
import { WebApp } from 'meteor/webapp';
import { createAuthMiddleware } from 'meteor/accounts-express';

const app = express();
app.use('/api', createAuthMiddleware({ required: true }));

app.get('/api/me', async (req, res) => {
  const user = await Meteor.userAsync();
  res.json({ userId: Meteor.userId(), email: user?.emails?.[0]?.address });
});

WebApp.handlers.use(app);
Read
references/architecture.md
for REST API module organization.
使用Express和
accounts-express
无缝构建带认证的REST端点:
js
import express from 'express';
import { WebApp } from 'meteor/webapp';
import { createAuthMiddleware } from 'meteor/accounts-express';

const app = express();
app.use('/api', createAuthMiddleware({ required: true }));

app.get('/api/me', async (req, res) => {
  const user = await Meteor.userAsync();
  res.json({ userId: Meteor.userId(), email: user?.emails?.[0]?.address });
});

WebApp.handlers.use(app);
如需了解REST API模块组织方式,请阅读
references/architecture.md

React Integration

React集成

Meteor's reactive data layer connects to React through
useTracker
(hooks) or
withTracker
(HOC):
jsx
import { useTracker } from 'meteor/react-meteor-data';

function TodoList() {
  const { todos, user } = useTracker(() => ({
    todos: Todos.find().fetch(),
    user: Meteor.user(),
  }));

  return todos.map(t => <TodoItem key={t._id} todo={t} user={user} />);
}
Read
references/react-integration.md
for
withTracker
patterns, subscription lifecycle in components, and common pitfalls.

Meteor的响应式数据层通过
useTracker
(钩子)或
withTracker
(高阶组件)与React连接:
jsx
import { useTracker } from 'meteor/react-meteor-data';

function TodoList() {
  const { todos, user } = useTracker(() => ({
    todos: Todos.find().fetch(),
    user: Meteor.user(),
  }));

  return todos.map(t => <TodoItem key={t._id} todo={t} user={user} />);
}
如需了解
withTracker
模式、组件中的订阅生命周期以及常见陷阱,请阅读
references/react-integration.md

Project Structure

项目结构

A typical Meteor 3 + React project:
my-app/
├── client/              # Client entry, main.jsx, global styles
│   └── main.jsx         # Meteor.startup(() => render(<App />))
├── server/              # Server entry, publications, startup
│   ├── main.js          # Meteor.startup, indexes, seeds
│   └── publications/    # Pub definitions by domain
├── imports/             # Shared code (lazy-loaded by convention)
│   ├── api/             # Collections, methods, schemas
│   │   ├── todos/
│   │   │   ├── collection.js
│   │   │   ├── methods.js
│   │   │   └── publications.js
│   │   └── users/
│   ├── ui/              # React components
│   │   ├── components/  # Reusable UI
│   │   ├── pages/       # Route-level components
│   │   └── layouts/     # Layout wrappers
│   └── startup/         # Client/server bootstrap
├── public/              # Static assets (served as-is)
├── private/             # Server-only assets (Assets API)
├── .meteor/             # Meteor internals, packages, versions
└── package.json
Key conventions:
  • Everything under
    imports/
    is lazy — only loaded when explicitly imported
  • client/
    and
    server/
    directories are eagerly loaded on their respective sides
  • Files outside
    imports/
    that aren't in
    client/
    or
    server/
    load on both sides
Read
references/architecture.md
for circular dependency prevention, import rules, and module organization patterns.

典型的Meteor 3 + React项目结构:
my-app/
├── client/              # 客户端入口、main.jsx、全局样式
│   └── main.jsx         # Meteor.startup(() => render(<App />))
├── server/              # 服务器入口、发布、启动逻辑
│   ├── main.js          # Meteor.startup、索引、种子数据
│   └── publications/    # 按领域划分的发布定义
├── imports/             # 共享代码(按约定懒加载)
│   ├── api/             # 集合、方法、模式
│   │   ├── todos/
│   │   │   ├── collection.js
│   │   │   ├── methods.js
│   │   │   └── publications.js
│   │   └── users/
│   ├── ui/              # React组件
│   │   ├── components/  # 可复用UI组件
│   │   ├── pages/       # 路由级组件
│   │   └── layouts/     # 布局包装器
│   └── startup/         # 客户端/服务器启动引导
├── public/              # 静态资源(原样提供)
├── private/             # 服务器专属资源(通过Assets API访问)
├── .meteor/             # Meteor内部文件、包、版本信息
└── package.json
关键约定:
  • imports/
    下的所有代码均为懒加载——仅在显式导入时加载
  • client/
    server/
    目录下的代码会在对应端自动加载
  • 不在
    imports/
    client/
    server/
    中的文件会在客户端和服务器端同时加载
如需了解循环依赖预防、导入规则以及模块组织模式,请阅读
references/architecture.md

Common Patterns

常见模式

Error Handling in Methods

方法中的错误处理

Only
Meteor.Error
reaches the client — other exceptions are sanitized to a generic 500:
js
// Server method
async 'orders.cancel'(orderId) {
  const order = await Orders.findOneAsync(orderId);
  if (!order) throw new Meteor.Error('not-found', 'Order not found');
  if (order.userId !== this.userId) throw new Meteor.Error('not-authorized', 'Not your order');
  await Orders.updateAsync(orderId, { $set: { status: 'cancelled' } });
}

// Client
try {
  await Meteor.callAsync('orders.cancel', orderId);
} catch (err) {
  if (err.error === 'not-found') showToast(err.reason);
}
只有
Meteor.Error
会传递到客户端——其他异常会被处理为通用的500错误:
js
// 服务器方法
async 'orders.cancel'(orderId) {
  const order = await Orders.findOneAsync(orderId);
  if (!order) throw new Meteor.Error('not-found', '订单不存在');
  if (order.userId !== this.userId) throw new Meteor.Error('not-authorized', '这不是你的订单');
  await Orders.updateAsync(orderId, { $set: { status: 'cancelled' } });
}

// 客户端
try {
  await Meteor.callAsync('orders.cancel', orderId);
} catch (err) {
  if (err.error === 'not-found') showToast(err.reason);
}

Collection Helpers

集合助手

Attach computed properties and methods to documents using
dburles:collection-helpers
:
js
Todos.helpers({
  isOverdue() {
    return this.dueDate && this.dueDate < new Date();
  },
  owner() {
    return Meteor.users.findOne(this.userId);
  },
});

// Usage — any document from Todos.find/findOne gets these methods
const todo = Todos.findOne(id);
if (todo.isOverdue()) { /* ... */ }
使用
dburles:collection-helpers
为文档附加计算属性和方法:
js
Todos.helpers({
  isOverdue() {
    return this.dueDate && this.dueDate < new Date();
  },
  owner() {
    return Meteor.users.findOne(this.userId);
  },
});

// 使用方式——从Todos.find/findOne获取的任何文档都拥有这些方法
const todo = Todos.findOne(id);
if (todo.isOverdue()) { /* ... */ }

Authorization Pattern

授权模式

Guard methods and publications with
this.userId
:
js
Meteor.methods({
  async 'projects.archive'(projectId) {
    if (!this.userId) throw new Meteor.Error('not-authorized');
    const project = await Projects.findOneAsync(projectId);
    if (project.ownerId !== this.userId) {
      throw new Meteor.Error('forbidden', 'Only the owner can archive');
    }
    return await Projects.updateAsync(projectId, { $set: { archived: true } });
  },
});
使用
this.userId
保护方法和发布:
js
Meteor.methods({
  async 'projects.archive'(projectId) {
    if (!this.userId) throw new Meteor.Error('not-authorized');
    const project = await Projects.findOneAsync(projectId);
    if (project.ownerId !== this.userId) {
      throw new Meteor.Error('forbidden', '只有所有者可以归档项目');
    }
    return await Projects.updateAsync(projectId, { $set: { archived: true } });
  },
});

Accounts & Users

账户与用户

Meteor's built-in accounts system provides
Meteor.userId()
,
Meteor.user()
, and their async equivalents:
js
// Server — async required in v3
const user = await Meteor.userAsync();

// Client — sync is fine (reads from Minimongo)
const user = Meteor.user();
const userId = Meteor.userId();

// Reactive in useTracker
const user = useTracker(() => Meteor.user(), []);

// Async client logins
await Meteor.loginWithPasswordAsync(email, password);
await Meteor.loginWithTokenAsync(token);
Meteor内置的账户系统提供
Meteor.userId()
Meteor.user()
以及它们的异步版本:
js
// 服务器端——v3版本必须使用异步
const user = await Meteor.userAsync();

// 客户端——同步调用即可(读取Minimongo数据)
const user = Meteor.user();
const userId = Meteor.userId();

// 在useTracker中响应式获取
const user = useTracker(() => Meteor.user(), []);

// 客户端异步登录
await Meteor.loginWithPasswordAsync(email, password);
await Meteor.loginWithTokenAsync(token);

Sending Email

发送邮件

Always use
Email.sendAsync
on the server — it returns a Promise and fits the async-first model:
js
import { Email } from 'meteor/email';

Meteor.methods({
  async 'notifications.send'(to, subject, html) {
    if (!this.userId) throw new Meteor.Error('not-authorized');
    await Email.sendAsync({ from: 'noreply@example.com', to, subject, html });
  },
});

在服务器端请始终使用
Email.sendAsync
——它返回Promise并符合异步优先模型:
js
import { Email } from 'meteor/email';

Meteor.methods({
  async 'notifications.send'(to, subject, html) {
    if (!this.userId) throw new Meteor.Error('not-authorized');
    await Email.sendAsync({ from: 'noreply@example.com', to, subject, html });
  },
});

What to Watch Out For

注意事项

1. "Can't set timers inside simulations"

1. "Can't set timers inside simulations"

This browser error occurs when an async method stub is running (simulation context is active) and a
withTracker
/
useTracker
component re-renders, calling
Meteor.defer
. Fix: add
if (Meteor.isClient) return;
at the top of complex stubs to make them no-ops on the client. The server still runs the full logic; Minimongo updates via the subscription.
当异步方法存根运行时(模拟上下文处于激活状态),
withTracker
/
useTracker
组件重新渲染并调用
Meteor.defer
,会触发此浏览器错误。修复方法:在复杂存根顶部添加
if (Meteor.isClient) return;
,使其在客户端成为空操作。服务器仍会运行完整逻辑;Minimongo通过订阅更新数据。

2.
Meteor.call
with async stubs

2. 异步存根搭配
Meteor.call

Meteor.call
was designed for sync stubs. Always use
Meteor.callAsync
in v3. Mixing them causes stubs to not resolve properly.
Meteor.call
是为同步存根设计的。在v3版本中请始终使用
Meteor.callAsync
。混用两者会导致存根无法正确解析。

3. Circular dependencies in barrel imports

3. 桶式导入中的循环依赖

Mixed barrels that re-export models, components, actions, and schemas from a single index file are the #1 source of
Element type is invalid
and
undefined
errors in Meteor apps. Prefer direct file imports on hot paths.
在单个索引文件中重新导出模型、组件、操作和模式的混合桶式导入,是Meteor应用中
Element type is invalid
undefined
错误的首要原因。在热路径中优先使用直接文件导入。

4. Returning non-EJSON values from methods

4. 从方法返回非EJSON值

Method return values must be EJSON-serializable (plain objects, arrays, strings, numbers, dates, binary, ObjectID). Functions, class instances, and circular references will fail.
方法返回值必须是可EJSON序列化的(纯对象、数组、字符串、数字、日期、二进制数据、ObjectID)。函数、类实例和循环引用会导致失败。

5. Publication setup vs cursor return

5. 发布设置与游标返回

Publication functions can be
async
for setup work, but must return a sync cursor or call
this.ready()
:
js
Meteor.publish('items.forTeam', async function (teamId) {
  const team = await Teams.findOneAsync(teamId);
  if (!team.members.includes(this.userId)) return this.ready();
  return Items.find({ teamId });  // sync cursor for reactivity
});
发布函数可以是
async
用于设置工作,但必须返回同步游标或调用
this.ready()
js
Meteor.publish('items.forTeam', async function (teamId) {
  const team = await Teams.findOneAsync(teamId);
  if (!team.members.includes(this.userId)) return this.ready();
  return Items.find({ teamId });  // 同步游标用于响应性
});

6. Always use field projections in
find()
/
findOneAsync()

6. 在
find()
/
findOneAsync()
中始终使用字段投影

Fetching full documents when you only need a few fields wastes memory, bandwidth, and serialization time. Pass a
fields
(or
projection
) option whenever you don't need the whole document:
js
// Bad — fetches every field on every matching document
const names = await Users.find({ active: true }).fetchAsync();

// Good — only pull what you need
const names = await Users.find({ active: true }, { fields: { username: 1, email: 1 } }).fetchAsync();

// In publications — reduces data pushed over DDP
Meteor.publish('todos.titles', function () {
  return Todos.find({ userId: this.userId }, { fields: { text: 1, done: 1, createdAt: 1 } });
});
This matters especially in publications: every extra field is serialized and pushed to every subscribed client. See
references/performance.md
for projection strategies.
当只需要部分字段时获取完整文档会浪费内存、带宽和序列化时间。只要不需要整个文档,就传递
fields
(或
projection
)选项:
js
// 不良写法——获取匹配文档的所有字段
const names = await Users.find({ active: true }).fetchAsync();

// 良好写法——仅获取所需字段
const names = await Users.find({ active: true }, { fields: { username: 1, email: 1 } }).fetchAsync();

// 在发布中使用——减少通过DDP推送的数据量
Meteor.publish('todos.titles', function () {
  return Todos.find({ userId: this.userId }, { fields: { text: 1, done: 1, createdAt: 1 } });
});
这在发布中尤为重要:每个额外字段都会被序列化并推送给每个订阅客户端。如需了解投影策略,请阅读
references/performance.md

7.
rawCollection()
bypasses collection hooks

7.
rawCollection()
会绕过集合钩子

rawCollection()
(used for bulk operations/native Mongo methods) bypasses Meteor's collection hooks. You must manually replicate side-effects (e.g.,
updatedAt
, sync logic). See
references/collections-models.md
.
rawCollection()
(用于批量操作/原生Mongo方法)会绕过Meteor的集合钩子。你必须手动复制副作用(如
updatedAt
、同步逻辑)。请阅读
references/collections-models.md

8. DDP queue blocking — async methods still block each other

8. DDP队列阻塞——异步方法仍会互相阻塞

Even in Meteor 3, where methods are natively
async
, the DDP server preserves sequential per-client execution by default. A method awaiting a slow external API will block all subsequent method calls from that same client until it resolves. Fix: call
this.unblock()
at the top of methods that are safe to run in parallel (after auth guards). Never unblock write methods whose results are consumed immediately by a follow-up method from the same client (race condition). See
references/performance.md
for the full guide including the
this.unblock()
vs.
Meteor.defer()
decision matrix.
即使在原生支持
async
的Meteor 3中,DDP服务器默认仍保留按客户端顺序执行的规则。等待慢速外部API的方法会阻塞同一客户端的所有后续方法调用,直到它解析完成。修复方法:在安全的并行方法顶部调用
this.unblock()
(在认证检查之后)。对于结果会被同一客户端后续方法立即使用的写入方法,切勿调用
this.unblock()
(会导致竞态条件)。如需完整指南,包括
this.unblock()
Meteor.defer()
的决策矩阵,请阅读
references/performance.md

9. Multiple publications, same collection, different projections — MergeBox wins unpredictably

9. 多个发布、同一集合、不同投影——MergeBox的合并结果不可预测

When two active subscriptions publish the same document
_id
into the same collection name but with different
fields
projections, Meteor's MergeBox merges them on the client. The rules:
  • Top-level fields are unioned — if pub A publishes
    title
    and pub B publishes
    body
    , the client doc gets both. Good.
  • Conflicting top-level fields are resolved arbitrarily — if both pubs publish
    status
    but with different values, one wins. Which one? Unspecified.
  • No deep merge — if pub A sends
    { profile: { name: 'Alice' } }
    and pub B sends
    { profile: { age: 30 } }
    , the entire
    profile
    object comes from whichever publication "wins" for that field. The other sub-fields silently disappear.
  • Unsub surprise — when one subscription stops, the MergeBox removes the fields it contributed. A component that assumed a field exists may suddenly see
    undefined
    .
The fix: virtual collections. When you need the same document published with genuinely different shapes (e.g., list view vs. full detail), publish into separate client-side collection names:
js
// server — two publications, two DDP collection namespaces
Meteor.publish('messages.list', function (channelId) {
  // Lightweight: just what the list UI needs
  return Messages.find(
    { channelId },
    { fields: { authorId: 1, preview: 1, createdAt: 1 } }
  );
  // DDP collection name defaults to 'messages'
});

Meteor.publish('messages.full', function (messageId) {
  // Use low-level API to push into a DIFFERENT client collection name
  const self = this;
  const doc = Messages.findOne(messageId); // or use cursor + observe
  if (doc) self.added('messagesFull', doc._id, doc);
  self.ready();
});
js
// client — two separate Minimongo collections, no merge conflict
export const Messages     = new Mongo.Collection('messages');     // list view
export const MessagesFull = new Mongo.Collection('messagesFull'); // detail view
Naming convention:
MessagesFull
,
MessagesStripped
,
MessagesList
— whatever communicates the intended field shape. The key is that each virtual collection has a single, stable field contract.
Use this pattern whenever: (a) you need different field shapes of the same document in the same session, (b) you have a public list projection and a richer authenticated detail projection, or (c) you've seen fields mysteriously disappear when a second subscription activates.

当两个活跃订阅将同一文档
_id
发布到同一集合名称
但使用不同
fields
投影时,Meteor的MergeBox会在客户端合并它们。规则如下:
  • 顶级字段会被合并——如果发布A推送
    title
    ,发布B推送
    body
    ,客户端文档会同时拥有这两个字段。这是合理的。
  • 冲突的顶级字段会被任意解析——如果两个发布都推送
    status
    但值不同,其中一个会生效。具体哪一个?未指定。
  • 不支持深度合并——如果发布A发送
    { profile: { name: 'Alice' } }
    ,发布B发送
    { profile: { age: 30 } }
    ,整个
    profile
    对象会来自在该字段上"获胜"的发布。另一个发布的子字段会无声消失。
  • 取消订阅的意外情况——当一个订阅停止时,MergeBox会移除它贡献的字段。假设某个字段存在的组件可能会突然看到该字段变为
    undefined
修复方案:虚拟集合。当需要在同一会话中发布同一文档的不同结构(如列表视图 vs 完整详情)时,发布到不同的客户端集合名称:
js
// 服务器端——两个发布,两个DDP集合命名空间
Meteor.publish('messages.list', function (channelId) {
  // 轻量版:仅列表UI所需字段
  return Messages.find(
    { channelId },
    { fields: { authorId: 1, preview: 1, createdAt: 1 } }
  );
  // DDP集合名称默认为'messages'
});

Meteor.publish('messages.full', function (messageId) {
  // 使用底层API推送到不同的客户端集合名称
  const self = this;
  const doc = Messages.findOne(messageId); // 或使用游标+observe
  if (doc) self.added('messagesFull', doc._id, doc);
  self.ready();
});
js
// 客户端——两个独立的Minimongo集合,无合并冲突
export const Messages     = new Mongo.Collection('messages');     // 列表视图
export const MessagesFull = new Mongo.Collection('messagesFull'); // 详情视图
命名约定:
MessagesFull
MessagesStripped
MessagesList
——只要能传达预期的字段结构即可。关键在于每个虚拟集合都有单一、稳定的字段约定
当以下情况时使用此模式:(a) 需要在同一会话中使用同一文档的不同字段结构,(b) 有公开列表投影和更丰富的认证详情投影,(c) 遇到第二个订阅激活时字段神秘消失的情况。

Reference Files

参考文档

For deeper coverage, read these when working on specific areas:
FileWhen to read
references/methods-rpc.md
Stubs, optimistic UI,
callAsync
vs
applyAsync
, error handling,
this.name
references/pubsub.md
Composite publications, SubsManager, reactive counts, data flow, MergeBox / virtual collection pattern
references/react-integration.md
useTracker
vs
withTracker
, component lifecycle, container patterns
references/collections-models.md
Schemas, helpers, indexes, aggregation,
rawCollection
hooks pitfall
, MongoDB Collation
references/async-patterns.md
Fibers→async migration, async method/publication patterns
references/architecture.md
Project structure, import rules, circular dependency prevention, REST API (
accounts-express
)
references/performance.md
this.unblock()
/ DDP queue parallelism
,
Meteor.defer()
, MongoDB Change Streams, DDP Session Resumption, publication polling optimization, auth separation, field projections

如需深入了解特定领域,请在对应场景下阅读以下文档:
文件阅读场景
references/methods-rpc.md
存根、乐观UI、
callAsync
vs
applyAsync
、错误处理、
this.name
references/pubsub.md
复合发布、SubsManager、响应式计数、数据流、MergeBox / 虚拟集合模式
references/react-integration.md
useTracker
vs
withTracker
、组件生命周期、容器模式
references/collections-models.md
模式、助手、索引、聚合、
rawCollection
钩子陷阱
、MongoDB排序规则
references/async-patterns.md
Fibers→异步迁移、异步方法/发布模式
references/architecture.md
项目结构、导入规则、循环依赖预防、REST API(
accounts-express
references/performance.md
this.unblock()
/ DDP队列并行性
Meteor.defer()
MongoDB变更流、DDP会话恢复、发布轮询优化、认证分离、字段投影

Code Style Defaults

代码风格默认规则

Unless the project specifies otherwise:
  • Use named exports (avoid default exports)
  • Name files after the primary export
  • Prefer direct file imports over barrel imports for React components, actions, and schemas
  • Use
    async
    /
    await
    everywhere on the server — never rely on sync collection APIs
  • Guard all methods and publications with
    this.userId
    checks
  • Throw
    Meteor.Error(errorCode, reason)
    for client-visible errors
  • Use
    npm
    as the package manager
除非项目另有规定:
  • 使用命名导出(避免默认导出)
  • 文件名与主要导出内容一致
  • 对于React组件、操作和模式,优先使用直接文件导入而非桶式导入
  • 在服务器端所有地方使用
    async
    /
    await
    ——绝不依赖同步集合API
  • 使用
    this.userId
    检查保护所有方法和发布
  • 对于客户端可见的错误,抛出
    Meteor.Error(errorCode, reason)
  • 使用
    npm
    作为包管理器