Relay QueryRenderer STORE_THEN_NETWORK 如何实现部分缓存渲染?
解决方案:利用Relay缓存的部分数据逐步渲染
针对你遇到的问题——从Foo导航到Bar时,希望先基于缓存中的NavBar_data渲染NavBar,再等待Bar_data加载完成后完整渲染,同时允许NavBar数据处于stale状态——这里有几个可行的方案,按推荐优先级排序:
1. 使用@defer指令拆分查询(推荐,Relay Modern+支持)
Relay的@defer指令可以让你把查询拆分为"立即返回的核心数据"和"延迟加载的补充数据",完美匹配你的场景:
# BarQuery 改造后 query BarQuery { viewer { ...NavBar_data # 这部分会立即从缓存/网络返回 ...Bar_data @defer # 这部分会延迟加载,不阻塞初始渲染 } }
当从Foo跳转到Bar时:
- Relay会先从缓存中读取
NavBar_data,立即返回包含viewer和NavBar_data的partial props,触发第一次渲染(NavBar正常显示,Bar可以展示loading状态) - 同时后台请求
Bar_data,数据返回后,Relay会自动更新组件,渲染完整的Bar内容 - 因为用了
STORE_THEN_NETWORK,如果NavBar_data是stale的,Relay会同时发起网络请求更新它,但不会阻塞初始渲染
2. 拆分查询为多层独立的Relay容器/Hooks
如果你的项目暂不支持@defer,可以把NavBar和Bar的数据查询拆分开,让它们各自独立利用缓存:
方案A:用QueryRenderer嵌套
// App组件只负责路由,NavBar单独用QueryRenderer function App({ children }) { return ( <> <QueryRenderer environment={environment} query={NavBarQuery} # 只查询...NavBar_data dataFrom="STORE_THEN_NETWORK" render={({ props }) => props ? <NavBar data={props.viewer} /> : <NavBarLoading />} /> {children} <Footer /> </> ); } // Bar页面的QueryRenderer只查询...Bar_data function BarPage() { return ( <QueryRenderer environment={environment} query={BarOnlyQuery} # query { viewer { ...Bar_data } } dataFrom="STORE_THEN_NETWORK" render={({ props }) => props ? <Bar data={props.viewer} /> : <BarLoading />} /> ); }
这样从Foo到Bar时:
- NavBar的QueryRenderer会直接读取缓存中的
NavBar_data,立即渲染 - Bar的QueryRenderer去请求
Bar_data,加载完成后渲染Bar内容
方案B:用Relay Hooks(更简洁)
如果你的项目用Relay Hooks,可以用useFragment共享viewer上下文,同时拆分数据依赖:
// App组件用useLazyLoadQuery获取基础viewer数据,然后传给子组件 function App({ children }) { const data = useLazyLoadQuery( query AppQuery { viewer { id } }, {}, { fetchPolicy: 'store-and-network' } ); return ( <> <NavBar viewer={data.viewer} /> {children} <Footer /> </> ); } // NavBar用useFragment获取自己需要的数据 function NavBar({ viewer }) { const navData = useFragment( fragment NavBar_data on Viewer { ...NavBar_data }, viewer ); // 这里如果缓存有数据会立即渲染,否则等待网络 return <nav>{navData.navTitle}</nav>; } // Bar组件同理 function Bar() { const viewer = useFragment( fragment Bar_viewer on Viewer { id }, useViewer() ); const barData = useFragment( fragment Bar_data on Viewer { ...Bar_data }, viewer ); return <div>{barData.barContent}</div>; }
这种方式下,每个组件只请求自己需要的数据,缓存复用更高效,从Foo到Bar时NavBar的缓存数据会直接被复用,无需等待。
3. 自定义QueryRenderer的渲染逻辑(兼容旧版本)
如果你不想改动查询结构,可以在QueryRenderer的render回调中手动判断partial数据,提前渲染可用部分:
<QueryRenderer environment={environment} query={BarQuery} dataFrom="STORE_THEN_NETWORK" render={({ error, props, retry }) => { if (error) return <div>加载出错,请重试</div>; // 检查缓存中是否有NavBar需要的核心数据 const hasValidNavBarData = props?.viewer && typeof props.viewer.navBarTitle !== 'undefined' && Array.isArray(props.viewer.navLinks); return ( <App> {/* 有NavBar数据就渲染,否则显示NavBar加载状态 */} {hasValidNavBarData ? <NavBar data={props.viewer} /> : <NavBarSkeleton />} {/* Bar数据存在就渲染,否则显示加载 */} {props?.viewer?.barContent ? <Bar data={props.viewer} /> : <BarLoading />} </App> ); }} />
Relay的STORE_THEN_NETWORK模式下,只要缓存中有部分匹配的数据,就会先返回partial props,然后等网络请求完成后返回完整数据。所以你只需要在渲染逻辑中判断哪些字段已经可用,就可以提前渲染对应的UI部分。
关键注意事项
- 确保你的Fragment定义准确:Relay依赖Fragment来跟踪组件的字段依赖,所以
NavBar_data和Bar_data的Fragment必须明确列出所有需要的字段,这样Relay才能正确识别缓存中的partial数据。 - stale数据的处理:
STORE_THEN_NETWORK本身就会优先返回缓存数据(即使stale),然后用网络数据更新,所以不需要额外配置,只要你的渲染逻辑支持partial数据即可。 - 版本兼容性:
@defer需要Relay Modern及以上版本,且你的GraphQL服务器需要支持defer指令(比如Apollo Server、GraphQL Yoga等都支持)。
内容的提问来源于stack exchange,提问作者Thomas Grainger
相关产品推荐
相关产品推荐

