You need to enable JavaScript to run this app. 如何正确向Apollo缓存的分页字段值中添加新对象
优惠活动
大模型
产品
解决方案
定价
更多

如何正确向Apollo缓存的分页字段值中添加新对象

解决方案

报错原因

你通过readQuery从Apollo Client缓存中读取的所有数据对象默认会被Object.freeze冻结,禁止直接修改以避免污染缓存。你之前只对comments对象做了浅拷贝,内部的edges数组仍然指向缓存中被冻结的原数组,所以调用push方法时会抛出不可扩展的错误。

最佳实现方案

Apollo Client原生支持Optimistic UI,官方推荐搭配optimisticResponse+update回调实现,无需手动处理请求中状态、成功/失败回滚逻辑:

  1. optimisticResponse会在请求发起时立刻返回你构造的假响应数据
  2. update回调会用这份假数据更新缓存,立即渲染新评论
  3. 若请求成功,Apollo会自动用服务端返回的真实数据替换缓存中的假数据;若请求失败,会自动回滚缓存变更,无需额外处理

完整代码示例

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

// 你的创建评论Mutation
const CREATE_COMMENT_MUTATION = gql`
  mutation CreateComment($articleId: ID!, $text: String!) {
    createComment(articleId: $articleId, text: $text) {
      id
      text
      liked
      likes
      createdAt
      owner {
        id
        firstName
        lastName
        username
        numericId
      }
    }
  }
`;

const ArticleDetail = ({ articleId, currentUser }) => {
  const [createComment, { loading }] = useMutation(CREATE_COMMENT_MUTATION, {
    // 构造乐观响应
    optimisticResponse: (variables) => ({
      __typename: 'Mutation',
      createComment: {
        __typename: 'Comment',
        id: `temp-${Date.now()}`, // 临时唯一ID,服务端返回后会自动替换
        text: variables.text,
        liked: false,
        likes: 0,
        createdAt: new Date().toISOString(),
        owner: {
          __typename: 'UserBase',
          ...currentUser
        }
      }
    }),
    // 更新缓存逻辑
    update: (cache, { data: { createComment } }) => {
      // 读取缓存中的文章数据
      const { getArticle } = cache.readQuery({
        query: GET_ARTICLE_QUERY,
        variables: { id: articleId }
      });

      // 构造新的评论分页数据,所有修改的字段都要新建引用
      const updatedComments = {
        ...getArticle.comments,
        totalCount: getArticle.comments.totalCount + 1,
        pageInfo: {
          ...getArticle.comments.pageInfo,
          count: getArticle.comments.pageInfo.count + 1,
          // 若新评论插在列表末尾,更新endCursor;插在开头则更新startCursor
          endCursor: btoa(createComment.id) // Cursor格式根据你后端规则调整,通常是Base64编码的评论ID
        },
        edges: [
          ...getArticle.comments.edges, // 新建数组,避免修改原冻结数组
          {
            __typename: 'CommentEdge',
            node: createComment,
            cursor: btoa(createComment.id)
          }
        ]
      };

      // 将更新后的数据写回缓存
      cache.writeQuery({
        query: GET_ARTICLE_QUERY,
        variables: { id: articleId },
        data: {
          getArticle: {
            ...getArticle,
            comments: updatedComments
          }
        }
      });
    }
  });

  // 提交评论的触发逻辑示例
  const handleSubmitComment = (text) => {
    createComment({
      variables: {
        articleId,
        text
      }
    });
  };

  // 其余组件逻辑省略
}

注意事项

  • 不要直接修改从缓存读取的任何对象/数组,所有需要变更的层级都要通过展开运算符新建引用即可,不需要全量深拷贝,只会浪费性能
  • 临时ID保证唯一即可,不需要和后端ID规则一致,Apollo会自动在请求完成后替换为真实ID
  • 如果你的评论列表是最新评论排在最前,把新edge放在edges数组的开头,同时更新pageInfo.startCursor即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.21 04:24:03