FEATURED · 精选文章

Meteor Session 响应式客户端状态存储:API 用法与 Hot Code Push 数据保留原理

发布时间 / 2026/9/19 3:32:14
来源 / 创域科博编辑部
栏目 / 资讯中心
Meteor Session 响应式客户端状态存储:API 用法与 Hot Code Push 数据保留原理 Meteor Session 响应式客户端状态存储API 用法与 Hot Code Push 数据保留原理【免费下载链接】meteorMeteor, the JavaScript App Platform项目地址: https://gitcode.com/gh_mirrors/me/meteorSession 是 Meteor 客户端内置的全局响应式键值存储本质上是带命名的ReactiveDict实例用于保存 UI 状态如表格当前选中行、弹窗是否打开并能在 Hot Code Push热代码推送时保留内容。本文以 packages/session/README.md 为主线结合包源码、ReactiveDict 底层实现与官方 API 文档完整讲解 Session 的安装、四个核心 API、响应式失效机制以及跨 HCP 数据迁移的实现细节。一、Session 是什么按 packages/session/README.md 的定义This package providesSession.Sessionis a specialReactiveDictwhose contents are preserved across Hot Code Push. Its usually used to store the current state of the user interface.Session 是客户端全局唯一的响应式字典对象专门服务于界面瞬时状态的存取例如表格中当前选中的行、某个对话框是否打开的布尔标志等。它具备两个关键特性响应式Reactive在模板或Tracker.autorun中读取的值会在对应键被写入新值时自动触发重渲染或计算重跑HCP 保留Migration应用发布新版本触发热代码推送时已存在的键值对会自动迁移到新版本不会因客户端代码热更新而丢失。完整 API 文档见仓库内 docs/source/api/session.md。二、安装与包结构Session 是客户端专用包locus Client在应用目录下执行meteor add session从 packages/session/package.js 可以看到包的关键声明Package.describe({ summary: Session variable, version: 1.2.3, }); Package.onUse(function (api) { api.use([ecmascript, reactive-dict], client); // Session can work with or without reload, but if reload is present // it should load first so we can detect it at startup and populate // the session. api.use(reload, client, { weak: true }); api.export(Session, client); api.mainModule(session.js, client); api.addAssets(session.d.ts, server); });值得注意的包级细节reload以weak方式依赖Session 在有无 reload 包的环境下都能工作但若存在 reload它必须先于 reload 加载从而在启动时检测到迁移数据并回填 SessionSession只在客户端导出api.export(Session, client)服务端不存在该对象包同时提供了 TypeScript 声明文件 packages/session/session.d.tsTS 项目可直接获得类型提示。三、Session 的本质一个命名 ReactiveDict整个包的实现极其精简packages/session/session.js 的核心只有一行import { ReactiveDict } from meteor/reactive-dict; export const Session new ReactiveDict(session);也就是说Session 并不是独立实现的数据结构而是带名字session的ReactiveDict单例。这一点被测试直接验证packages/session/session_tests.jsTinytest.add(session - Session is a ReactiveDict instance, function (test) { test.instanceOf(Session, ReactiveDict); });在 packages/reactive-dict/reactive-dict.js 的构造函数中当传入字符串dictName时会执行两条与迁移相关的逻辑仅客户端Meteor.isClient ReactiveDict._registerDictForMigrate(dictName, this); const migratedData Meteor.isClient ReactiveDict._loadMigratedDict(dictName); if (migratedData) { this.keys migratedData; } else { this._setObject(dictData || {}); }这正是 Session 能够在 HCP 中保留数据的入口详见第五节。ReactiveDict类还额外提供all()、clear()、delete()、destroy()等方法Session 作为其实例同样可用只是官方文档主要暴露set/setDefault/get/equals四个方法。四、核心 API 与完整用法1. Session.set(key, value) —— 写入并通知设置会话变量并通知所有监听了该 key 的监听者重绘模板、重跑调用过Session.get的Tracker.autorun计算。value 必须是EJSON 可序列化的值对象、数组、Date、Mongo.ObjectID 等均可。Session.set(currentRoomId, home);与 Backbone 的Model.set类似set被重载为可一次性传入对象批量设置等价于对每个键分别调用setSession.set({ a: foo, b: bar });官方文档中的订阅示例展示了它的响应式威力——Tracker.autorun内部读取Session.get一旦 key 变化整个订阅函数自动重跑Tracker.autorun(() { Meteor.subscribe(chatHistory, { room: Session.get(currentRoomId) }); }); // 触发上面的 autorun 重跑将 chatHistory 订阅切换到 home 房间 Session.set(currentRoomId, home);2. Session.setDefault(key, value) —— 仅当未设置时写入如果该 key 之前没有被设置过则写入否则行为与set完全相同且不触发任何失效。官方文档明确指出其典型场景是初始化代码避免每次加载应用新版本时都重新初始化会话变量。从 packages/reactive-dict/reactive-dict.js 的源码看它的实现正是先判断 key 是否存在setDefault(keyOrObject, value) { if ((typeof keyOrObject object) (value undefined)) { this._setDefaultObject(keyOrObject); // 批量形式 return; } const key keyOrObject; if (! hasOwn.call(this.keys, key)) { this.set(key, value); } }典型用法是在应用启动时设置 UI 默认状态例如默认展开的侧栏、默认选中的标签页等。对应测试session - setDefault on an already-set key does not invalidatepackages/session/session_tests.js验证了已存在的 key 上调用 setDefault 不会导致 autorun 重跑。3. Session.get(key) —— 读取并建立响应式依赖返回 key 对应的值若处于响应式计算中模板助手、Tracker.autorun则在下一次该值被Session.set改变时使计算失效重跑。重要语义返回值是会话值的克隆。如果存储的是对象或数组直接修改返回值不会影响 Session 中保存的值测试session - objects are cloned对此有专门验证见 packages/session/session_tests.jsSession.set(frozen-array, [1, 2, 3]); Session.get(frozen-array)[1] 42; // 修改克隆 test.equal(Session.get(frozen-array), [1, 2, 3]); // 原值不受影响模板中的典型用法!-- main.html -- template namemain pWeve always been at war with {{theEnemy}}./p /template// main.js Template.main.helpers({ theEnemy() { return Session.get(enemy); } }); Session.set(enemy, Eastasia); // 页面显示 Weve always been at war with Eastasia Session.set(enemy, Eurasia); // 页面自动更新为 Weve always been at war with Eurasia4. Session.equals(key, value) —— 高效相等判断判断会话变量是否等于某个值若处于响应式计算中则在该变量变为该值或离开该值时使计算失效。对于标量值下面两个表达式效果相同Session.get(key) value Session.equals(key, value)但第二个总是更优它触发更少的失效模板重绘程序更高效。原因在第五节源码分析中详述。官方文档给出了经典的选中项高亮示例用户点击列表项后只有新选中与旧取消选中的两项会重渲染若改用Session.get则当选中项变化时所有列表项都会重渲染template namepostsView {{#each posts}} {{ postItem}} {{/each}} /template template namepostItem div class{{postClass}}{{title}}/div /templateTemplate.postsView.helpers({ posts() { return Posts.find(); } }); Template.postItem.helpers({ postClass() { return Session.equals(selectedPost, this._id) ? selected : ; } }); Template.postItem.events({ click() { Session.set(selectedPost, this._id); } });5. 对象/数组值的比较限制对于对象和数组类型的会话值不能使用Session.equals。原因从 packages/reactive-dict/reactive-dict.js 的实现注释可见JSON.stringify无法保证对象键顺序的规范化canonicalize因此无法为对象构造稳定的每值依赖索引。当传入非标量时实现会直接抛错throw new Error(ReactiveDict.equals: value must be scalar);测试中对此有明确断言packages/session/session_tests.jstest.throws(function () { Session.equals(arr, [1, 2, { a: 1, b: [5, 6] }]); });此时应改用underscore包的_.isEqual_.isEqual(Session.get(key), value);需要注意一个例外实现层面equals额外放行了Date和Mongo.ObjectIDvalue instanceof Date或value instanceof ObjectID不会抛错对应测试也覆盖了这两种类型见 packages/session/session_tests.js。因此日期、ObjectID 这类标量语义值可以安全地使用equals。五、底层原理响应式失效与 TrackerSession 的响应式完全继承自 ReactiveDict其内部维护了三套依赖allDeps全局依赖all()使用keyDepskey →Tracker.Dependencyget()使用keyValueDepskey → serializedValue →Tracker.Dependencyequals()使用。值序列化与相同值不重跑所有存储的值在写入前都会经stringify序列化packages/reactive-dict/reactive-dict.jsfunction stringify(value) { if (value undefined) { return undefined; } return EJSON.stringify(value); }set会比较新旧序列化字符串只有真正变化才触发依赖changed()。因此设置相同的值不会重跑autorun测试session - context invalidation for get验证了Session.set(x, 1)两次之间不重跑但1和1序列化后不同会触发重跑Session.set(x, 1)后重跑一次。get 的失效粒度按 keyget(key)调用this.keyDeps[key].depend()注册依赖。任何对该 key 的set都会触发keyDeps[key].changed()导致所有读取该 key 的计算重跑。equals 的失效粒度按值equals(key, value)在Tracker.active时建立的是keyValueDeps[key][serializedValue]上的依赖packages/reactive-dict/reactive-dict.js。也就是说只有变量的值变化到或离开被比较的那个值时才失效。这就是选中项高亮示例中只有新旧两项重渲染的原因——每个列表项只关心selectedPost是否等于自己的_id而关心同一个值的计算会被收敛到同一 Dependency 上。实现还包含一个内存优化依赖被注销Tracker.onInvalidate且无其他依赖者时会删除keyValueDeps[key][serializedValue]以释放内存。失效时机flush 才生效测试session - context invalidation for get/equals还验证了一个重要时序Session.set之后、Tracker.flush()之前autorun不会立即重跑重跑发生在 flush 阶段。因此在同一轮 flush 中连续多次set计算只会重跑一次。undefined 的特判equals(key, undefined)有专门的特殊处理session - context invalidation for equals with undefined测试从未设置、显式设为undefined、设为undefined字符串等场景下依赖与比较结果均有精确预期同时Session.get(key)对未设置的 key 返回undefinedSession.equals(key, undefined)返回true测试session - get/set/equals types第一段断言。六、Hot Code Push 数据保留机制这是 Session 区别于普通ReactiveDict的关键卖点。机制实现在 packages/reactive-dict/migration.js核心是两个静态注册表ReactiveDict._migratedDictData {}; // name - data ReactiveDict._dictsToMigrate {}; // name - ReactiveDict整体流程分三步启动加载构造函数中_registerDictForMigrate(dictName, this)把自己登记到_dictsToMigrate随后_loadMigratedDict(dictName)从_migratedDictData中取出旧数据并直接替换this.keys注意迁移数据不再二次序列化注册迁移回调若存在 reload 包客户端通过Package.reload.Reload._onMigrate(reactive-dict, ...)注册回调在 HCP 发生时遍历所有已登记字典调用_getMigrationData()即原始this.keys汇总数据Package.reload.Reload._onMigrate(reactive-dict, function () { var dataToMigrate {}; for (var dictName in dictsToMigrate) dataToMigrate[dictName] dictsToMigrate[dictName]._getMigrationData(); return [true, {dicts: dataToMigrate}]; });新版本回填新代码启动时从Reload._migrationData(reactive-dict)读出数据放入_migratedDictData供各命名字典按名字取回。由此可以归纳出几条使用约束命名唯一_registerDictForMigrate对重名会抛出Duplicate ReactiveDict name。Session 固定使用session所以应用内不要再创建名为session的 ReactiveDict迁移依赖 reload 包reload 是 weak 依赖若项目未包含 reload 包Session 依然可用但失去 HCP 保留能力适用范围迁移数据由 reload 包在客户端会话内传递Session 适合保存热更新期间需要延续的 UI 瞬时状态需要跨浏览器会话、跨设备或长期持久化的数据应使用localStorage/IndexedDB 或服务端存储而不是 Session。七、TypeScript 类型定义packages/session/session.d.ts 中提供了完整的类型签名与运行时行为一一对应export namespace Session { function equals(key: string, value: string | number | boolean | any): boolean; function get(key: string): any; function set(key: string, value: EJSONable | any): void; function setDefault(key: string, value: EJSONable | any): void; }注意get返回any且是克隆值类型层面无法自动区分已设置与未设置两者都返回undefined建议在读取时做显式默认值处理。八、实践建议与注意事项综合官方文档docs/source/api/session.md与源码总结实践要点只存 UI 状态不存业务数据如选中项 id、标签页索引、筛选条件快照不要存放长列表、大对象或可从服务端重新计算的数据初始化用setDefault在启动代码中设置默认值避免新版本热更新后重复初始化已有状态模板比较优先equals比较标量尤其选中项时用equals而非get显著减少重渲染范围对象/数组比较用_.isEqual(Session.get(key), value)理解克隆语义get返回克隆修改返回值不会写回 Session需要整体更新对象/数组时构造新值重新set注意类型严格性set内部走 EJSON 序列化1与1、true与1都被视为不同值测试中Session.equals(f, 1)为falseundefined与null也是不同的会话值清理测试残留测试代码通过delete Session.keys[key]手动清除状态注释提到当时尚无Session.clear()现在 Session 作为 ReactiveDict 实例可直接调用Session.clear()清空全部键或在不需要 HCP 保留时调用Session.destroy()该命名字典将从迁移注册表中移除。九、未来方向packages/session/README.md 在 Future work 中提出与reactive-dict统一Unify with reactive-dict。事实上 Session 与 ReactiveDict 在实现上已高度同源Session 就是 ReactiveDict 实例完整 API 见 packages/reactive-dict/README.mdreactive-dict 的 Future work 则进一步提出与reactive-var统一。对开发者而言如果只是局部组件内的响应式状态可以直接使用ReactiveVar或命名ReactiveDict需要全局且 HCP 保留的 UI 状态时Session 仍是 Meteor 客户端开箱即用的标准方案。参考文件索引包说明packages/session/README.md核心实现packages/session/session.js、packages/reactive-dict/reactive-dict.js迁移机制packages/reactive-dict/migration.js包声明packages/session/package.js类型定义packages/session/session.d.ts单元测试packages/session/session_tests.js官方 API 文档docs/source/api/session.md【免费下载链接】meteorMeteor, the JavaScript App Platform项目地址: https://gitcode.com/gh_mirrors/me/meteor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻