ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Relay 数据驻留与垃圾回收指南:理解 presence-of-data、Query Retention 与 GC 策略

Relay 数据驻留与垃圾回收指南:理解 presence-of-data、Query Retention 与 GC 策略 前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载Relay 在客户端内存 Store 中缓存查询结果以便在无需网络请求的情况下复用数据、快速渲染页面。但数据不会永远驻留Relay 会通过垃圾回收Garbage Collection删除不再被任何组件引用的数据。本文基于 Relay 官方指南presence-of-data.md展开系统讲解数据何时存在于 Store、何时被回收以及如何通过environment.retain、gcScheduler、gcReleaseBufferSize精确控制数据的保留时长帮助你在“复用缓存”与“控制内存”之间找到平衡。数据何时存在缓存的生命周期理解 Relay 缓存复用的第一步是弄清楚数据在 Store 中的生命周期lifetime——即数据是否存在于 Store 中以及它会存在多久。基本规律如下查询首次被获取后该查询及其变量对应的数据会写入 Relay Store只要该查询正在屏幕上被渲染其数据一般会持续存在从未被获取过的查询其数据自然缺失于 Store 中。也就是说数据的存在与否首先取决于“是否被获取过”其次取决于“是否仍在被使用”。但这里有一个不可避免的矛盾随着应用不断获取不同的查询Store 中的数据会无限累积变得过大、过陈旧stale。为了控制内存占用Relay 运行一个名为**垃圾回收Garbage Collection**的进程删除不再使用的数据。设计要点垃圾回收的存在与“复用缓存”的目标天然存在张力。如果数据被过早删除后续再次复用时就会落空导致必须重新发起网络请求才能渲染界面。因此如何让想要复用的数据在需要的时间内保持缓存是本节乃至整条 reusing-cached-data 指南线的核心命题。Relay 的垃圾回收机制Relay 对本地内存 Store 执行垃圾回收的方式是删除任何不再被应用中任何组件引用的数据。从实现层面看这一机制由 RelayModernStore 承担关键逻辑如下每个查询operation在 Store 内部对应一个root entry记录在_roots一个Map中并维护一个引用计数refCount见 RelayModernStore.js#L115-L123调用environment.retain()会使对应 root entry 的refCount加一释放dispose时减一见 RelayModernStore.js#L383-L440当refCount降到 0 时该查询的数据进入“可被回收”状态——但如果配置了 release buffer会先进入缓冲期GC 的实际执行由_collect()生成器与_gcStep()分步驱动通过gcScheduler调度见 RelayModernStore.js#L854-L886。值得注意的细节即使某个查询从未被显式 retain只要gcReleaseBufferSize 0且 release buffer 未满查询数据在写入时也会被临时记入_roots并放入 release buffer以便近期内复用见_recordSourceOperationRelayModernStore.js#L606-L634。:::note 通常你不需要操心垃圾回收与数据保留的配置——这些应由应用基础设施在RelayEnvironment层面统一配置。本文内容供理解与参考属于“知道底层如何运转”的知识储备。 :::Query Retention手动保留查询数据保留retain一个查询是向 Relay 表明该查询及其变量对应的数据不应被垃圾回收删除。多个调用方可以同时保留同一个查询只要至少有一个调用方还在保留该查询的数据就不会从 Store 中被删除。默认行为组件挂载期间的自动保留默认情况下任何使用useQueryLoader/usePreloadedQuery及其他相关 API 的查询组件会在自身挂载期间自动保留所渲染的查询。组件卸载后它们会**释放release**该查询——这意味着此后任意时刻该查询的数据都可能被删除。这套自动保留逻辑对应 Store 中 root entry 引用计数的增减组件挂载时计数加一卸载时计数减一归零即进入可回收状态。在组件生命周期之外保留environment.retain如果需要在组件的生命周期之外继续保留某个查询可以使用environment.retain(operationDescriptor)操作// 保留查询这将阻止 Relay 对 // 该查询及其变量的数据执行垃圾回收 const disposable environment.retain(queryDescriptor); // 释放 disposable 将放开对该查询及其变量数据的保留 // 意味着如果它没有被其他地方保留 // 就可能被 Relay 的垃圾回收随时删除 disposable.dispose();如上所述这样可以做到即使查询组件已卸载仍继续保留查询数据让其他组件或未来再次挂载的同一组件能够复用这些保留的数据。完整示例从 graphql 标签到 OperationDescriptorenvironment.retain接收的是 Relay 内部的操作描述符OperationDescriptor而不是裸的graphql模板字符串。需要先用getRequest获取请求描述符再通过createOperationDescriptor绑定具体变量const { createOperationDescriptor, getRequest, graphql, } require(relay-runtime); // graphql 查询对象 const query graphql...; // 构造 Relay 内部的查询表示 const queryRequest getRequest(query); const queryDescriptor createOperationDescriptor( queryRequest, variables, ); // 保留查询这将阻止 Relay 对 // 该查询及其变量的数据执行垃圾回收 const disposable environment.retain(queryDescriptor); // 释放 disposable 将放开对该查询及其变量数据的保留 // 意味着如果它没有被其他地方保留 // 就可能被 Relay 的垃圾回收随时删除 disposable.dispose();更完整的用法可参考 retaining-queries.md其中同样强调Relay 会根据挂载的查询组件自动管理查询数据的保留因此产品代码中通常不需要直接调用retain。对于高级或特殊场景查询数据的保留应由基础设施层代码如 Router统一处理。适用场景典型的例子是路由级代码——当用户离开某个页面、组件卸载后Router 可以在导航层面保留该页面对应的查询数据使得用户返回时能瞬间渲染而无须等待网络请求。控制 Relay 的垃圾回收策略目前可以在创建 Relay Store 时提供2 个选项来控制垃圾回收行为。它们的真实签名可在 RelayModernStore 构造函数 中确认。GC Scheduler何时执行 GCgcScheduler是一个可传给 Relay Store 的函数用于决定一次 GC 执行应该被调度到何时运行// 示例调度函数 // 接收一个回调并将其调度到未来的某个时间执行 function gcScheduler(run: () void) { resolveImmediate(run); } const store new Store(source, {gcScheduler});要点默认行为如果不提供gcSchedulerRelay 会使用resolveImmediate来调度 GC。resolveImmediate是基于 Promise 的setImmediate替代方案将回调放到微任务队列中尽快执行见 resolveImmediate.js自定义策略可以提供自己的调度函数让 GC 比默认行为更不激进例如基于时间、基于 scheduler 优先级或其他启发式规则约定按惯例实现不应立即执行回调即不应同步调用run而应将其延后到合适的时机。源码中的调度链路为scheduleGC()创建_gcRun生成器后调用gcScheduler(_gcStep)_gcStep每执行完一步若未完成会再次调用gcScheduler调度下一步从而把一次完整 GC 分摊到多次调度中见 RelayModernStore.js#L854-L886。此外在乐观更新optimistic update期间可通过holdGC()挂起 GC待恢复后再执行。GC Release Buffer Size释放缓冲大小Relay Store 内部维护一个释放缓冲区release buffer用于在查询被其原始持有者释放后默认发生在渲染该查询的组件卸载时仍然临时保留指定数量的查询。这使得在用户返回之前访问过的页面、标签页或内容时更有可能且更容易复用缓存数据。配置方式是在创建 Store 时指定gcReleaseBufferSizeconst store new Store(source, {gcReleaseBufferSize: 10});要点缓冲区大小为 0等价于没有释放缓冲区查询一旦被释放就会被立即回收默认大小环境的 release buffer 大小为10。源码层面的对应关系RelayModernStore.js#L169-L170this._gcReleaseBufferSize options?.gcReleaseBufferSize ?? DEFAULT_RELEASE_BUFFER_SIZE; // 10缓冲区的运作机制RelayModernStore.js#L642-L651被释放refCount为 0的 root entry 会被推入_releaseBuffer当缓冲区长度超过gcReleaseBufferSize时最旧最早加入的条目被逐出evict并从_roots中删除随后调度 GC因此缓冲区越大越多的最近访问过的查询得以保留复用命中率越高但同时内存占用也越大——这是需要在内存与复用率之间权衡的配置。测试用例describe(GC with a release buffer, ...)明确验证了该行为例如“在 caller 释放后仍将数据保留在 release buffer 中”“如果数据不过期即使只发布数据也会被保留在 release buffer 中”见 RelayModernStore-test.js 附近的测试组。这从侧面印证release buffer 正是“页面/标签往返复用”的核心机制。实践建议综合官方指南与源码实现可总结出如下实践建议默认不动 GC 配置默认的gcSchedulerresolveImmediate与gcReleaseBufferSize 10已覆盖大多数场景。GC 与保留策略应由应用基础设施Environment 层、Router 层统一配置产品代码一般无需干预需要跨页面复用数据时用 retain若某个查询的数据在组件卸载后仍需被复用如返回上一页、切换标签页在 Router 等基础设施层调用environment.retain()并在适当时机dispose()是最直接、最可控的方式权衡 release buffer 大小增大gcReleaseBufferSize可提升近期访问数据的复用命中率但会增加内存占用设为 0 则关闭缓冲、立即回收适合对内存敏感的场景自定义 gcScheduler 控制回收时机若 GC 过于频繁导致卡顿可按时间或优先级延后调度注意约定上不要同步执行回调。延伸阅读缓存复用总览reusing-cached-data/introduction.md数据是否过期staleness-of-data.md获取策略store-or-network等fetch-policies.md手动保留查询的完整 APIretaining-queries.md核心实现RelayModernStore.js、resolveImmediate.js行为验证测试RelayModernStore-test.js、RelayModernEnvironment-Retain-test.js赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 数据驻留与垃圾回收全指南理解 Presence of Data、Query Retention 与 GC 策略Relay 数据驻留与垃圾回收全指南理解 Presence of Data、Query Retention 与 GC 策略 导读 本指南聚焦 React Re前端开发工具Relay 缓存数据的存续性理解 Relay 垃圾回收、Query Retention 与 GC 策略配置Relay 缓存数据的存续性理解 Relay 垃圾回收、Query Retention 与 GC 策略配置 Relay 的核心设计之一是内置的规范化缓存no前端开发工具Relay 缓存数据的存在性与垃圾回收掌握 Query Retention 与 GC 配置Relay 缓存数据的存在性与垃圾回收掌握 Query Retention 与 GC 配置 在 Relay 中复用缓存数据是一个高频且容易踩坑的主题只前端开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表