【问题标题】:Is there a way to detect when data used by a query is evicted from the Apollo Client cache?有没有办法检测查询使用的数据何时从 Apollo 客户端缓存中清除?
【发布时间】:2020-06-06 05:47:24
【问题描述】:

对于这个冗长的问题,我深表歉意,但我非常感谢您对 Apollo Client 3 中缓存失效和重新获取查询的最佳策略的一些想法/帮助。

背景

首先,关于我想象的场景的一些信息:

  • 有一个 Account 组件(如下示例),它使用 react-apollo 中的 useQuery 挂钩来获取和显示有关帐户的一些基本信息以及该帐户的交易列表
  • 在应用程序的其他地方,有一个CreateTransactionForm 组件使用突变插入新事务。这是一个单独的组件,它位于组件树中的不同位置,不一定是AccountComponent 的子组件
  • 至关重要的是,在服务器上存储事务的过程除了将实际事务插入数据库之外还有一些重要的副作用:
    • 所有其他交易发生在之后被插入(按时间顺序)的交易都会更新为新的运行余额
    • 任何相关帐户都会更新为新的当前余额

我的Account 组件的简化版本可能如下所示:

import { gql, useQuery } from '@apollo/client';
import React from 'react';
import { useParams } from 'react-router-dom';

const GET_ACCOUNT_WITH_TRANSACTIONS = gql`
  query getAccountWithTransactions($accountId: ID!) {
    account(accountId: $accountId) {
      _id
      name
      description
      currentBalance
      transactions {
        _id
        date
        amount
        runningBalance
      }
    }
  }
`;

export const Account: React.FunctionComponent = () => {
  const { accountId } = useParams();
  const { loading, error, data } = useQuery(GET_ACCOUNT_WITH_TRANSACTIONS, {
    variables: { accountId },
  });

  if (loading) { return <p>Loading...</p>; }
  if (error) { return <p>Error</p>; }

  return (
    <div>
      <h1>{data.account.name}</h1>

      {data.account.transactions.map(transaction => (
        <TransactionRow key={transaction._id} transaction={transaction} />
      ))}
    </div>
  );
};

潜在策略

我正在评估使 Apollo 客户端缓存的部分无效并在插入事务后重新获取适当数据的各种选项。根据我目前了解到的情况,有一些潜在的策略:

a) 调用useQuery 返回的refetch 方法强制Account 组件重新加载其数据

  • 这似乎是可靠的,并且会返回服务器获取新数据,但 CreateTransactionForm 需要(直接或间接)耦合到 Account 组件,因为需要触发对 refetch 的调用

b) 将查询名称 (getAccountWithTransactions) 传递给突变的 refetchQueries 选项

  • 类似于 a,但可能具有更紧密的耦合 - CreateTransactionForm 需要了解应用程序中存在并可能受突变影响的所有其他组件/查询(如果将来添加更多组件/查询) ,这意味着记得更新CreateTransactionForm)

c) 执行突变后手动修改缓存的内容

  • 我想这将非常复杂/难以维护,因为CreateTransactionForm 需要确切地知道哪些数据由于服务器的操作而发生了变化。如前所述,这可能不是微不足道的数据量,在执行突变后,我们不仅需要检索有关已插入事务的更新数据,还需要检索任何其他已作为副作用更新的其他数据,以及受影响的帐户,等等。它也可能不是很有效,因为其中一些信息可能永远不会在客户端中再次查看。

我的直觉是,以上选项都不理想。特别是,随着应用程序的增长,我担心可维护性;如果组件需要明确知道哪些其他组件/查询可能会受到对数据图的更改的影响,那么一旦应用程序变得越来越大,就会很容易错过一个并引入细微的错误复杂。

更好的方法?

我对 Apollo Client 3 中引入的新 evictgc 方法非常感兴趣,我想知道它们是否可以提供更简洁的解决方案。

我正在考虑的是,在调用突变后,我可以使用这些新功能:

  • 积极驱逐交易中包含的所有账户上的 transactions 数组
  • 另外,删除所有受影响帐户上的 currentBalance 字段

例如:

  const { cache } = useApolloClient();
  ...
  // after calling the mutation:
  cache.evict(`Account:${accountId}`, 'transactions');
  cache.evict(`Account:${accountId}`, 'currentBalance');
  cache.gc();

以上提供了一种从缓存中删除陈旧数据的简单方法,并确保组件将在下次执行这些字段查询时进入网络。例如,如果我导航到不同的页面并返回到 Account 页面,这会很有效。

我的主要问题(终于!)

这引出了我不确定的主要难题:

有什么方法可以检测到查询中引用的部分或全部数据已从缓存中清除?

我不确定这对库来说是否可行,但如果可能的话,我认为它可以简化代码并减少应用程序不同部分之间的耦合。

我的想法是,这将使每个组件变得更具“反应性”——组件只知道它们依赖哪些数据,并且每当底层缓存图中的数据丢失时,它可以立即通过触发自身重新获取来做出反应询问。组件最好以声明方式对它们所依赖的数据的变化做出反应,而不是在有意义的情况下强制通信以触发彼此的操作。

【问题讨论】:

  • 我通过驱逐字段成功触发了重新获取。我只是希望它有更好的记录。

标签: reactjs caching apollo apollo-client


【解决方案1】:

这里有两个答案,这样做的标准做法是什么?我们如何才能使这种更好的方式

标准做法

https://www.apollographql.com/blog/when-to-use-refetch-queries-in-apollo-client/

Apollo 在他们的博客和他们的文档中指出,如果您的突变对返回的查询有副作用之外,那么如果您想维护,您必须使用更新函数缓存一致性。这类似于使用refetchQueries,但在您自己更新缓存时更加明确(它也需要更少的网络请求)。虽然,您是对的,这种方法会导致您的事务的变异更新与所有其他依赖组件之间发生耦合。

我相信构建它的最佳方法是为所有变异具有副作用的组件定义一个更新函数。然后事务将调用它影响的所有组件更新函数。而这些反过来又会调用它们自己的依赖项。这可能会变得混乱,所以让我们看看您是否可以自己进行缓存控制。

更好的方法

https://www.apollographql.com/docs/apollo-server/data/data-sources/#using-memcachedredis-as-a-cache-storage-backend

我不相信您可以检查 Apollo 缓存是否存在数据,但您可以检查 Redis 缓存以获取相同的信息,并且 Apollo 允许您插入其他缓存实现。您甚至可以编写自己的缓存实现并将 Redis 用作实现的后端。这将允许您更好地控制所需的缓存。但是,这需要大量的前期工程成本。

结论:

由于您的副作用似乎在交易和帐户组件中(只有两个组件),我想说您现在应该编写一个自定义更新函数。一旦遇到更复杂的缓存一致性问题,我会考虑深入研究自定义缓存实现。

【讨论】:

    猜你喜欢
    • 2023-03-13
    • 2011-12-22
    • 2019-06-10
    • 2018-12-15
    • 2022-08-20
    • 2020-08-05
    • 1970-01-01
    • 2019-03-24
    • 2022-01-19
    相关资源
    最近更新 更多