资讯动态

Relay 查询刷新(Refreshing Queries)完整实战指南:useQueryLoader、useLazyLoadQuery 与 fetchPolicy 的正确用法

发布时间:2026/9/21 15:09:20 来源:尧图企业网站定制
前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载本文以 Relay 官方文档《Refreshing Queries》为核心系统讲解如何用完全相同的查询与变量重新向服务端拉取最新数据覆盖useQueryLoader/loadQuery与useLazyLoadQuery两种主流刷新方案、四种fetchPolicy的语义以及如何在需要时避开 Suspense 回退态。读完本文你将能针对获取最新计数下拉刷新数据重新同步等场景写出既正确又符合 Relay 数据获取模型的刷新代码并结合仓库源码理解其底层原理。什么是刷新查询Refreshing a Query在 Relay 的术语里刷新查询指的是重新获取查询最初渲染时所使用的同一份数据以便从服务端拿到这份数据的最新版本。它区别于用不同数据重新拉取refetching queries with different data例如换一个id重新请求。刷新场景的典型特征是查询与变量完全不变目的只是把陈旧的服务端数据更新到最新常见于Fetch latest count下拉刷新定时同步等交互。需要注意刷新查询与刷新 Fragment 是两个并列的话题Fragment 需要借助refetchable生成的查询来刷新参见 Refreshing Fragments。本文聚焦查询层面的刷新涉及的 Hook 分别是 useQueryLoader 与 useLazyLoadQuery。动手前先考虑能否使用实时特性在实现手动刷新之前官方文档给出的第一个建议是先评估是否适合使用实时real-time特性让数据自动保持最新从而省去周期性手动刷新。其中一个例子是使用 GraphQL Subscriptions。不过订阅方案通常需要额外的服务端配置以及网络层network layer支持例如配置 WebSocket 传输。如果你的后端暂不支持订阅、或者刷新动作本身是用户主动触发的交互如按钮点击那么下面介绍的两种手动刷新方案就是更直接的选择。前置知识四种 fetchPolicy 的语义刷新方案的核心都是通过fetchPolicy控制是否读本地缓存、是否发网络请求。仓库中packages/relay-runtime/util/RelayRuntimeTypes.js查看源码对FetchPolicy类型给出了精确定义export type FetchQueryFetchPolicy store-or-network | network-only; export type FetchPolicy FetchQueryFetchPolicy | store-and-network | store-only;四种取值的行为如下详见官方 Fetch Policies 一节fetchPolicy是否复用本地缓存是否发网络请求适用场景store-or-network默认是仅当缓存中数据缺失或过期时才请求常规渲染store-and-network是总是请求同时展示缓存追求实时性且可容忍新旧数据交替network-only否总是请求刷新场景首选store-only是从不请求刷新完成后读已缓存数据避免 Suspense两个关键实现事实默认策略在 loadQuery.js 源码中DEFAULT_FETCH_POLICY store-or-network而对于 live query含执行期 resolver 的查询默认策略会切换为DEFAULT_LIVE_FETCH_POLICY store-and-network以保证实时性。store-only 早退同一个文件里当fetchPolicy store-only时checkAvailabilityAndExecute会直接return查看源码完全跳过网络层——这正是避免 Suspense方案中先用fetchQuery预取、再用store-only读缓存能立刻出数据的原因。方案一使用useQueryLoader/loadQuery刷新useQueryLoader 是render-as-you-fetch模式的核心 Hook它返回[queryReference, loadQuery, disposeQuery]三元组。刷新时我们只需要用相同变量再次调用loadQuery即可。基础刷新配合 Suspense官方文档给出的示例由父子两个组件构成。父组件持有useQueryLoader把刷新回调与queryRef传给使用usePreloadedQuery的子组件/** * App.react.js */ import type {AppQuery as AppQueryType} from AppQuery.graphql; const AppQuery require(__generated__/AppQuery.graphql); function App(props: Props) { const [queryRef, loadQuery] useQueryLoaderAppQueryType( AppQuery, props.appQueryRef /* initial query ref */ ); const refresh useCallback(() { // Load the query again using the same original variables. // Calling loadQuery will update the value of queryRef. // The fetchPolicy ensures we always fetch from the server and skip // the local data cache. const {variables} props.appQueryRef; loadQuery(variables, {fetchPolicy: network-only}); }, [/* ... */]); return ( React.Suspense fallbackLoading query... MainContent refresh{refresh} queryRef{queryRef} / /React.Suspense ); }/** * MainContent.react.js */ import type {AppQuery as AppQueryType} from AppQuery.graphql; // Renders the preloaded query, given the query reference function MainContent(props) { const {refresh, queryRef} props; const data usePreloadedQueryAppQueryType( graphql query AppQuery($id: ID!) { user(id: $id) { name friends { count } } } , queryRef, ); return ( h1{data.user?.name}/h1 divFriends count: {data.user.friends?.count}/div Button onClick{() refresh()} Fetch latest count /Button / ); }拆解这段代码的核心机制在刷新的事件处理器中直接调用loadQuery网络请求立即开始随后把更新后的queryRef传给使用usePreloadedQuery的子组件从而渲染最新数据传入fetchPolicy: network-only确保总是走网络、跳过本地数据缓存由于network-only一定会发起网络请求loadQuery会触发组件重渲染并让usePreloadedQuery进入 Suspense 挂起状态详见 Loading States with Suspense。因此必须用Suspense边界包裹MainContent以展示回退加载态。源码印证为什么再次调用 loadQuery就能刷新在仓库的 loadQuery.js 中可以看到两个关键设计每次调用都会生成新的fetchKeyfetchKey查看源码。源码注释说明这保证每个新建的 query reference 传给usePreloadedQuery时都会被独立求值避免 Suspense 缓存直接复用旧结果、从而跳过重取。networkCacheConfig强制绕过网络层响应缓存loadQuery内部会合并{...options?.networkCacheConfig, force: true}查看源码确保即使网络层带有额外查询响应缓存也不会把旧响应直接复用。此外在 useQueryLoader.js 的实现里loadQuery回调会调用loadQuery(environment, preloadableRequest, variables, mergedOptions)并把返回的updatedQueryReference通过setQueryReference写入 state随后的 effect 会按插入顺序逐个releaseQuery/dispose旧的 query reference查看源码避免泄漏。这解释了为什么重复调用loadQuery是安全且被官方推荐的刷新方式。变体如果不想触发 Suspense在某些场景例如不希望回退态把已渲染内容整个隐藏掉下可以使用 fetchQuery 替代并自行维护加载状态/** * App.react.js */ import type {AppQuery as AppQueryType} from AppQuery.graphql; const AppQuery require(__generated__/AppQuery.graphql); function App(props: Props) { const environment useRelayEnvironment(); const [queryRef, loadQuery] useQueryLoaderAppQueryType( AppQuery, props.appQueryRef /* initial query ref */ ); const [isRefreshing, setIsRefreshing] useState(false) const refresh useCallback(() { if (isRefreshing) { return; } const {variables} props.appQueryRef; setIsRefreshing(true); // fetchQuery will fetch the query and write // the data to the Relay store. This will ensure // that when we re-render, the data is already // cached and we dont suspend fetchQuery(environment, AppQuery, variables) .subscribe({ complete: () { setIsRefreshing(false); // *After* the query has been fetched, we call // loadQuery again to re-render with a new // queryRef. // At this point the data for the query should // be cached, so we use the store-only // fetchPolicy to avoid suspending. loadQuery(variables, {fetchPolicy: store-only}); } error: () { setIsRefreshing(false); } }); }, [/* ... */]); return ( React.Suspense fallbackLoading query... MainContent isRefreshing{isRefreshing} refresh{refresh} queryRef{queryRef} / /React.Suspense ); }这个变体的关键点因为不再依赖 Suspense我们需要自行维护isRefreshing加载状态用它来在MainContent内部渲染 spinner 之类的忙碌 UI而不会隐藏MainContent本体事件处理器中先调用fetchQuery它会把查询结果写入本地 Relay store待其网络请求完成后再调用loadQuery拿到更新后的queryRef传给usePreloadedQuery渲染最新数据此刻该查询的数据已存在于 store 中因此使用fetchPolicy: store-only只读已缓存数据不触发网络请求也不会挂起通过if (isRefreshing) { return; }防止重复点击并发刷新error回调里同样重置isRefreshing保证错误后仍可再次刷新。结合 fetch-query 文档 需要补充两个注意事项fetchQuery会自动把结果写入 store并去重同查询同变量的在途请求但fetchQuery不会 retain保留数据请求结束后数据可能被垃圾回收如果需要在请求完成后长期保留应显式调用environment.retain()。这也正是本方案要在fetchQuery完成后再用loadQuery(variables, {fetchPolicy: store-only})重建 query reference 的原因——loadQuery内部会执行environment.retain(operation)查看源码保证刷新后的数据被持续保留。方案二使用useLazyLoadQuery刷新useLazyLoadQuery 在组件渲染期间惰性获取查询。刷新时思路是更新 state 传入新的fetchKey与fetchPolicy迫使查询被完全重新求值并重新拉取。基础刷新配合 Suspense/** * App.react.js */ import type {AppQuery as AppQueryType} from AppQuery.graphql; const AppQuery require(__generated__/AppQuery.graphql); function App(props: Props) { const variables {id: 4}; const [refreshedQueryOptions, setRefreshedQueryOptions] useState(null); const refresh useCallback(() { // Trigger a re-render of useLazyLoadQuery with the same variables, // but an updated fetchKey and fetchPolicy. // The new fetchKey will ensure that the query is fully // re-evaluated and refetched. // The fetchPolicy ensures that we always fetch from the network // and skip the local data cache. setRefreshedQueryOptions(prev ({ fetchKey: (prev?.fetchKey ?? 0) 1, fetchPolicy: network-only, })); }, [/* ... */]); return ( React.Suspense fallbackLoading query... MainContent refresh{refresh} queryOptions{refreshedQueryOptions ?? {}} variables{variables} / /React.Suspense ); }/** * MainContent.react.js */ import type {AppQuery as AppQueryType} from AppQuery.graphql; // Fetches and renders the query, given the fetch options function MainContent(props) { const {refresh, queryOptions, variables} props; const data useLazyLoadQueryAppQueryType( graphql query AppQuery($id: ID!) { user(id: $id) { name friends { count } } } , variables, queryOptions, ); return ( h1{data.user?.name}/h1 divFriends count: {data.user.friends?.count}/div Button onClick{() refresh()} Fetch latest count /Button / ); }拆解要点在刷新事件处理器中通过 setState 更新组件使MainContent其内部使用useLazyLoadQuery以新的fetchKey和fetchPolicy重渲染并在渲染时重新拉取查询每次更新都传入递增的fetchKey。给useLazyLoadQuery传入新的fetchKey会确保该查询被完全重新求值并重新拉取即便变量没有变化、组件没有被重挂载使用fetchPolicy: network-only确保总是走网络、跳过本地缓存由于network-only必然发起网络请求refresh中的状态更新会使组件挂起参见 Loading States with Suspense因此必须用Suspense边界包裹MainContent。源码印证fetchKey 如何驱动重新求值在 useLazyLoadQuery.js 的 Options 类型注释中官方对fetchKey的说明是传入不同的fetchKey会强制对当前查询与变量重新求值即使变量没变、组件没有重挂载类似给 React 组件传不同key触发重挂载。若fetchKey与上一次渲染不同当前查询会重新对照 store 求值并根据fetchPolicy与缓存状态决定是否重新拉取。底层实现位于 useLazyLoadQueryNode.js它会把forceUpdateKey与fetchKey组合成cacheBreaker ${forceUpdateKey}-${fetchKey ?? }再据此生成queryCacheIdentifier并用它调用QueryResource.prepareWithIdentifier(...)。也就是说fetchKey变了cache identifier 就变了QueryResource会把该查询当作一条全新的未缓存资源来处理从而触发完整重取——这就是必须递增 fetchKey 才能刷新的机制来源。变体如果不想触发 Suspense与方案一类似可以改用fetchQuery预取数据并自行维护isRefreshing状态/** * App.react.js */ import type {AppQuery as AppQueryType} from AppQuery.graphql; const AppQuery require(__generated__/AppQuery.graphql); function App(props: Props) { const variables {id: 4} const environment useRelayEnvironment(); const [refreshedQueryOptions, setRefreshedQueryOptions] useState(null); const [isRefreshing, setIsRefreshing] useState(false) const refresh useCallback(() { if (isRefreshing) { return; } setIsRefreshing(true); // fetchQuery will fetch the query and write // the data to the Relay store. This will ensure // that when we re-render, the data is already // cached and we dont suspend fetchQuery(environment, AppQuery, variables) .subscribe({ complete: () { setIsRefreshing(false); // *After* the query has been fetched, we update // our state to re-render with the new fetchKey // and fetchPolicy. // At this point the data for the query should // be cached, so we use the store-only // fetchPolicy to avoid suspending. setRefreshedQueryOptions(prev ({ fetchKey: (prev?.fetchKey ?? 0) 1, fetchPolicy: store-only, })); } error: () { setIsRefreshing(false); } }); }, [/* ... */]); return ( React.Suspense fallbackLoading query... MainContent isRefreshing{isRefreshing} refresh{refresh} queryOptions{refreshedQueryOptions ?? {}} variables{variables} / /React.Suspense ); }要点回顾自行维护isRefreshing加载状态在MainContent内部渲染忙碌 UI而不隐藏内容本体先调用fetchQuery把数据写入本地 store请求完成后更新 state使useLazyLoadQuery以新的fetchKey与fetchPolicy重渲染出最新数据由于此时数据已缓存在 store 中state 更新时使用fetchPolicy: store-only只读缓存、不挂起。官方在《OssAvoidSuspenseNote》中补充说明未来 React 支持并发渲染后可能会提供在挂起时避免用回退态隐藏已渲染内容的原生选项查看原文。在此之前手动维护 loading state store-only重渲染是避免隐藏已渲染内容的推荐做法。两种方案对比与选型建议维度useQueryLoader/loadQueryuseLazyLoadQuery刷新手段用相同变量再次调用loadQuery递增fetchKey 更新fetchPolicy请求启动时机事件处理器内立即发起重渲染时发起配套渲染 HookusePreloadedQueryuseLazyLoadQuery是否推荐用于渲染查询官方更推荐render-as-you-fetch请求可更早开始可用但存在瀑布式/渲染期请求的潜在性能开销避免 Suspense 的变体fetchQueryloadQuery(..., store-only)fetchQuery 更新fetchKey与store-only选型建议如果你已经持有queryRef例如由路由或loadQuery预取产生选择方案一刷新逻辑最简洁如果查询完全由组件内部自行懒加载选择方案二无论哪种方案刷新语义都依赖network-only强制走网络而避免挂起则依赖store-only读缓存两者搭配使用才能既拿到新数据又不闪回退态若刷新动作是低频、用户主动触发的如按钮点击两种方案均适用若需要定时轮询等高频刷新建议优先评估 GraphQL Subscriptions 等实时方案参见上游文档。注意事项与最佳实践Suspense 边界不可省只要使用network-only刷新就必然触发一次网络请求并让渲染挂起务必在消费数据的组件外层包裹Suspense否则会报错或出现未处理挂起。不要在渲染阶段调用loadQueryuseQueryLoader返回的loadQuery回调如果被同步调用非事件处理器会抛出错误参见 use-query-loader 文档 的 Behavior 说明。刷新动作应始终放在事件处理器或 effect 中。并发防护与错误恢复if (isRefreshing) { return; }可以防止连点导致并发请求务必在complete与error两个回调中都重置isRefreshing避免状态卡死。fetchQuery不 retain 数据只用fetchQuery拉数据的话请求完成后的数据可能被 GC避免 Suspense方案中的loadQuery(..., store-only)/ 更新 fetchKey 重渲染同时承担了 retain 与渲染更新两个职责。与 Fragment 刷新保持一致的思路如果数据由 Fragment 承载可参考useRefetchableFragmentrefetchable的refetch函数其同样接受fetchPolicynetwork-only 刷新 / store-only 避免挂起仓库测试useRefetchableFragmentNode-test.js中大量使用refetch({}, {fetchPolicy: network-only})验证该行为查看测试。需要说明的是Fragment 无法脱离查询单独取数据必须依附一个refetchable生成的可重取查询。默认 fetchPolicy 足以覆盖多数首次渲染场景日常渲染使用默认的store-or-network即可只在明确需要强制刷新时切换到network-only避免无谓的重复网络请求。相关文档与源码索引官方指南Refreshing Queriesv13 版本文档、当前版本文档、Refetching 导论关联指南Fetch Policies、Refreshing Fragments、Loading States with Suspense、Fetching Queries for RenderAPI 参考useQueryLoader、useLazyLoadQuery、fetchQuery源码实现loadQuery.js、useQueryLoader.js、useLazyLoadQuery.js、useLazyLoadQueryNode.js、RelayRuntimeTypes.js相关测试useRefetchableFragmentNode-test.js、loadQuery-test.js、useLazyLoadQueryNode-test.js赞分享前端开发工具【免费下载链接】relayRelay is a JavaScript framework for building>项目地址https://gitcode.com/gh_mirrors/relay29/relay点击查看免费下载相关推荐Relay 查询刷新Refreshing Queries实战指南基于 useQueryLoader、useLazyLoadQuery 与 fetchQuery 的完整实现Relay 查询刷新Refreshing Queries实战指南基于 useQueryLoader 、 useLazyLoadQuery 与 fetchQ前端开发工具Relay 查询重取Refetching Queries with Different Data基于 useQueryLoader 与 useLazyLoadQuery 的换参重取完整指南Relay 查询重取Refetching Queries with Different Data基于 useQueryLoader 与 useLazyLo前端开发工具Relay 查询渲染与获取完整指南usePreloadedQuery、useQueryLoader、loadQuery 与 useLazyLoadQuery 实战解析Relay 查询渲染与获取完整指南usePreloadedQuery、useQueryLoader、loadQuery 与 useLazyLoadQuery前端开发工具上一篇如何在5分钟内搭建IRCAnywhere从安装到首次连接的完整指南下一篇vscode-shellcheck测试与调试如何确保你的扩展稳定运行创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价