【问题标题】:Should a query in Apollo Client look for the results cached by different queries before making a network request?Apollo Client 中的查询是否应该在发出网络请求之前查找不同查询缓存的结果?
【发布时间】:2021-02-23 12:17:12
【问题描述】:

我试图弄清楚 Apollo 客户端中的查询应该如何与缓存交互。

具体来说,我想知道我们是否运行一个获取所有待办事项的查询:

todos {
  title
  completed
}

然后我们运行一个查询,该查询获取一个已由 todos 查询获取的单个 todo,并请求完全相同的字段:

todo(id: $id) {
  title
  completed
} 

第二个查询应该 a) 从缓存中获取数据,还是 b) 发出网络请求?

我的假设是情况 A。这是基于 Apollo 官方博客文章中的引用:

https://www.apollographql.com/blog/demystifying-cache-normalization/

例如,如果我们要:

  1. 执行 GetAllTodos 查询、规范化和缓存来自后端的所有待办事项
  2. 对我们已经使用 GetAllTodos 检索到的待办事项调用 GetTodoById

...那么 Apollo 客户端可以直接进入缓存并直接获取对象,而无需发出另一个请求。

但是,在我的应用中,我不断收到案例 B,即使我已经在不同的查询中请求了所有数据,它也总是发出额外的网络请求。

我认为我做错了什么,所以我查看了这个 Apollo 全栈教程 repo (https://github.com/apollographql/fullstack-tutorial) 并更新了 LaunchDetails 查询以仅请求 GetLaunchList 查询中已经请求的相同数据。这复制了我在上面用 todos 详细描述的相同场景。

查询现在如下所示:

export const GET_LAUNCHES = gql`
  query GetLaunchList($after: String) {
    launches(after: $after) {
      cursor
      hasMore
      launches {
        ...LaunchTile
      }
    }
  }
  ${LAUNCH_TILE_DATA}
`;
export const GET_LAUNCH_DETAILS = gql`
  query LaunchDetails($launchId: ID!) {
    launch(id: $launchId) {
      ...LaunchTile
    }
  }
  ${LAUNCH_TILE_DATA}
`;

我运行了应用程序,发现为 LaunchDetails 查询发出了一个新的网络请求,尽管在运行 GetLaunchList 查询之后所有需要的数据都已经在缓存中。

我无法在文档中找到任何答案,而且我从示例教程应用程序中看到的结果似乎与上面博客文章中的引用不一致。

如果查询之前已经运行过,查询是否只会查找缓存?如果数据是由不同的查询缓存的,它不能获取缓存的数据吗?我错过了什么吗?

【问题讨论】:

    标签: caching apollo apollo-client


    【解决方案1】:

    查询将进行网络查询。

    todo(id: $id) {
      title
      completed
    } 
    

    Apollo 缓存不是很智能。它只是存储。更复杂的操作需要手动读写。

    原因是 Apollo 不知道您的架构和数据结构。它不知道todo(id: $id)会做DB搜索,所以它无法优化到缓存中查找。

    如果您不想要第二次提取,则必须使用片段实现数据提取结构:

    try {
      return client.readFragment({
        id: 'Todo:5', // The value of the to-do item's unique identifier
        fragment: gql`
          fragment TodoFragment on Todo {
          id
          title
          completed 
        }
        `,
      });
    } catch(_e) { // if no fragment is found there will be an error
      client.query(QUERY, variables: { id: 5})
    }
    

    Apollo 缓存的方式是,如果你做两个查询:

    1. 加载待办事项
    todos {
      id
      title
      completed
    }
    
    1. 加载单个待办事项
    todo(id: $id) {
      id
      title
      completed
    } 
    

    如果您列出待办事项列表并加载第二个 - 它将更新待办事项数据。

    【讨论】:

    • 谢谢。它为我清除了它,您的解决方案解决了我的问题。我猜阿波罗缓存没有我想象的那么聪明!我觉得 Apollo 团队应该更新我链接到的博客文章,这非常具有误导性。
    【解决方案2】:

    请在此处更好地(在我看来)答案:

    https://stackoverflow.com/a/66053242/6423036

    直接从该答案复制,感谢作者:

    这个功能是存在的,但如果你不知道你在找什么,就很难找到。在 Apollo Client v2 中,您正在寻找缓存重定向功能,在 Apollo Client v3 中,这被类型策略/字段读取策略 (v3 docs) 所取代。

    Apollo 不“了解”您的 GraphQL 架构,这使得在日常使用中设置和使用变得很容易。然而,这意味着给定一些查询(例如getBooks)它不知道结果类型将是什么预先。只要启用了__typename,它之后就知道了。这是默认行为,是规范化缓存所必需的。

    假设您有一个获取Books 列表的getBooks 查询。如果您在此请求完成后使用 Apollo devtools 检查缓存,您应该使用 Book:123 键在缓存中找到书籍,其中 Book 是类型名,123 是 id。如果它存在(并且被查询!)id 字段被用作缓存的标识符。如果你的 id 字段有别的名字,你可以使用缓存的 typePolicies 通知 Apollo InMemoryCache 这个字段。

    如果您进行了设置,然后运行 ​​getBook 查询,使用一些 id 作为输入,您将不会获取任何缓存数据。原因如前所述:Apollo 事先并不知道该查询将返回哪种类型。

    因此,在 Apollo v2 中,您将使用 cacheRedirect 将 Apollo “重定向”到正确的缓存:

      cacheRedirects: {
        Query: {
          getBook(_, args, { getCacheKey }) {
            return getCacheKey({
              __typename: 'Book',
              id: args.id,
            });
          }
        },
      },
    

    (如果您在 typePolicy 中指定了另一个键,args.id 应替换为另一个标识符)

    在使用 Apollo v3 时,需要一个 typepolicy / 字段读取策略:

      typePolicies: {
        Query: {
          fields: {
            getBook(_, { args, toReference }) {
              return toReference({
                __typename: 'Book',
                id: args.id,
              });
            }
          }
        }
      }
    

    【讨论】:

      猜你喜欢
      • 2022-01-09
      • 2019-10-05
      • 2020-03-22
      • 2022-12-22
      • 2016-12-12
      • 2020-02-11
      • 1970-01-01
      • 2021-08-05
      • 2018-07-30
      相关资源
      最近更新 更多