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 }, });
原因说明
useQuery的cache-only策略只会匹配已经被该查询完整写入过的根查询缓存条目,不会自动从独立的归一化实体节点拼接字段。哪怕你要的Creator实体字段确实存在于缓存中,只要GET_IS_FOLLOW_CREATOR这个查询从来没有被成功执行、也没有被手动写入根查询关联记录,就会返回null。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
相关产品推荐
相关产品推荐

