You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 04:00:16