Apollo Client删购物车最后1件商品需刷新才生效问题排查
问题现象
实现支持用户自主删除商品的购物车功能时出现差异化异常:
- 购物车内商品数量大于1时,删除操作正常生效,无需刷新页面即可看到最新结果,控制台可正常输出cart信息日志
- 购物车仅剩余1件商品时,执行删除操作后商品不会即时移除,控制台无对应日志输出,必须手动刷新页面才能展示删除成功的结果
现有实现代码
基于refetchQueries配置的删除Mutation初始实现
const [deleteProduct, { loading: deleteLoading, error: deleteError }] = useMutation(DELETE_FROM_CART, { variables: { productId, userId: cart?.userId }, refetchQueries: [ { query: GET_USER_CART, variables: { userId: cart?.userId }, awaitRefetchQueries: true, }, ], });
GET_USER_CART查询定义与useQuery调用代码
const GET_USER_CART = gql` query ($userId: ID!) { getUserCart(userId: $userId) { userId cartProducts { productId size productPrice } } } `; const { data: cartData, loading: cartLoading } = useQuery(GET_USER_CART, { variables: { userId: userInfo?.id }, });
手动更新缓存的Mutation实现(仍存在相同问题)
const [deleteProduct, { loading: deleteLoading, error: deleteError }] = useMutation(DELETE_FROM_CART, { variables: { productId, userId: cart?.userId }, update(cache, { data }) { const updatedCart = data?.deleteProductFromCart; const existCart = cache.readQuery({ query: GET_USER_CART, variables: { userId: cart?.userId }, }); if (existCart && updatedCart) { cache.writeQuery({ query: GET_USER_CART, variables: { userId: cart?.userId }, data: { getUserCart: { ...existCart.getUserCart, updatedCart }, }, }); } }, });
排查方向(按优先级从高到低验证)
- 优先检查缓存写入的结构错误
手动更新缓存的实现存在明显结构问题:代码将updatedCart作为全新字段塞进getUserCart对象,但查询定义里getUserCart本身仅包含userId、cartProducts两个字段,不存在updatedCart字段。当最后一件商品被删除时,后端返回的deleteProductFromCart一般是cartProducts: []结构,甚至直接返回null表示空购物车,错误的写入结构会导致Apollo缓存识别不到合法变更,自然不会触发组件重渲染。
修正缓存写入逻辑时,直接用后端返回的完整购物车数据覆盖即可,不要额外嵌套字段:cache.writeQuery({ query: GET_USER_CART, variables: { userId: cart?.userId }, data: { getUserCart: updatedCart // 保证updatedCart结构和查询返回的getUserCart完全一致,包含userId、cartProducts字段 } }) - 检查缓存查询的变量一致性
注意useQuery拉取购物车时用的变量是userId: userInfo?.id,但mutation中无论是refetch配置还是读写缓存操作,用的变量都是userId: cart?.userId。如果这两个值存在类型差异(比如一个是数字类型1,一个是字符串类型"1"),Apollo会将其识别为两个完全独立的缓存条目:删除最后一件商品时更新的是cart?.userId对应的缓存,但组件订阅的是userInfo?.id对应的缓存,自然不会触发视图更新。多商品场景下能生效大概率是巧合,比如其他操作刚好触发了缓存同步。
将所有用到userId的位置统一为同一个来源的值,保证查询变量完全一致即可。 - 检查空值边界的处理逻辑
当购物车最后一件商品被删除时,后端返回的getUserCart可能直接为null(空购物车无对应实体),现有逻辑无论refetch还是手动写缓存,都没有处理getUserCart: null的场景:手动更新缓存的判断条件if (existCart && updatedCart)会直接跳过null值写入,refetch时如果返回null,Apollo默认可能保留旧缓存值不更新。
可以给useQuery添加nextFetchPolicy: 'network-only'配置,或者调整缓存更新的判断逻辑,允许写入null值。 - 检查渲染层的判断逻辑
排查购物车列表的渲染代码,确认是否存在类似cart?.cartProducts?.length && <CartList/>的判断,是否有逻辑在商品数量为1时提前return,导致组件不响应数据变更。
内容的提问来源于stack exchange,提问作者עמית שוקרון
相关产品推荐
相关产品推荐

