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

Apollo Client如何订阅fragment缓存变更实现自动更新响应

问题说明

需要实现直接读取Apollo Client归一化缓存中指定fragment数据,同时在缓存变更时自动获取最新值的能力,目前遇到两个问题:

  • cache.readFragment可以正常读取缓存,但没有响应式订阅能力,缓存更新后不会自动同步
  • 给useQuery设置fetchPolicy: 'cache-only'时,即使对应实体字段存在于缓存中,返回的data仍然为null

当前两种实现代码如下:

// 单次读取缓存
const data = client.cache.readFragment({
  id: client.cache.identify({
    __typename: 'Creator',
    id: creatorId,
  }),
  fragment: IS_FOLLOWING_FRAGMENT,
}) as MyCreatorIsFollowing;
// 尽管缓存中存在对应数据,返回结果仍为null
const { data } = useQuery(GET_IS_FOLLOW_CREATOR, {
  fetchPolicy: 'cache-only',
  variables: { id: creatorId },
});

原因说明
  1. useQuery的cache-only策略只会匹配已经被该查询完整写入过的根查询缓存条目,不会自动从独立的归一化实体节点拼接字段。哪怕你要的Creator实体字段确实存在于缓存中,只要GET_IS_FOLLOW_CREATOR这个查询从来没有被成功执行、也没有被手动写入根查询关联记录,就会返回null。
  2. cache.readFragment是命令式单次读取API,设计上就没有绑定响应式更新逻辑,调用一次只会返回当下的缓存快照,不会感知后续的缓存变更。

实现方案

推荐方案:使用原生useFragment Hook(Apollo Client 3.2+ 支持)

这个Hook就是专门为该场景设计的,读取逻辑和readFragment完全一致——直接匹配归一化缓存中的实体节点,不需要提前执行对应Query,同时自带响应式订阅能力,只要fragment选中的字段在缓存中被修改(mutation更新、其他Query写入、手动修改缓存都会触发),组件会自动重渲染拿到最新值。
代码示例:

import { useFragment, gql } from '@apollo/client';

// 原有fragment定义保持不变
const IS_FOLLOWING_FRAGMENT = gql`
  fragment MyCreatorIsFollowing on Creator {
    id
    isFollowing
    # 其他需要的字段
  }
`;

function CreatorFollowStatus({ creatorId }) {
  const { data, complete } = useFragment({
    fragment: IS_FOLLOWING_FRAGMENT,
    // 直接传入实体标识,不需要手动调用cache.identify
    from: {
      __typename: 'Creator',
      id: creatorId,
    },
  });

  // data会自动跟随缓存更新,不需要额外处理
  return (
    <div>
      {complete ? (data.isFollowing ? '已关注' : '未关注') : '加载中'}
    </div>
  );
}

兼容方案:低版本Apollo Client手动封装订阅Hook

如果你的Apollo Client版本低于3.2,没有内置useFragment,可以基于底层的cache.watchFragment API自己封装带订阅能力的读取Hook:

import { useEffect, useState } from 'react';
import { useApolloClient } from '@apollo/client';

function useSubscribedFragment(readOptions) {
  const client = useApolloClient();
  // 初始化时直接读取当前缓存值
  const [data, setData] = useState(() => client.cache.readFragment(readOptions));

  useEffect(() => {
    // 入参变化时先同步一次最新缓存值
    const latestData = client.cache.readFragment(readOptions);
    setData(latestData);

    // 订阅对应fragment的缓存变更
    const subscription = client.cache.watchFragment({
      ...readOptions,
      callback: (diff) => {
        if (!diff.complete) return;
        setData(diff.result);
      }
    });

    // 组件卸载时取消订阅
    return () => subscription.unsubscribe();
  }, [client, JSON.stringify(readOptions)]);

  return { data };
}

// 组件中使用
function CreatorFollowStatus({ creatorId }) {
  const client = useApolloClient();
  const { data } = useSubscribedFragment({
    id: client.cache.identify({
      __typename: 'Creator',
      id: creatorId,
    }),
    fragment: IS_FOLLOWING_FRAGMENT,
  });

  return <div>{data?.isFollowing ? '已关注' : '未关注'}</div>;
}

备选方案:调整useQuery使用逻辑

如果你一定要用useQuery实现,不要用cache-only策略,第一次使用cache-first策略发起一次请求完成初始缓存写入,之后Apollo会自动把归一化实体的变更广播给所有引用了对应字段的Query,缓存更新时会自动同步最新值。
这个方案的缺点是必须至少完成一次初始查询写入(要么发网络请求,要么手动写入对应Query的缓存结果),否则第一次读取仍然会返回null,灵活度远低于直接读fragment的方案。

注意:所有方案生效的前提是,你更新缓存时(比如mutation的update回调、cache.modify调用)是直接修改对应归一化实体节点的字段,而不是只修改某个Query返回的临时副本,否则变更不会被广播到订阅者。


内容的提问来源于stack exchange,提问作者Scaraux

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 06:09:32