有没有一种方法可以检测何时从Apollo客户端缓存中清除了查询所使用的数据?

时间:2020-02-21 18:56:02

标签: reactjs caching apollo apollo-client

很抱歉,这个问题很长,但是对于在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 Client缓存部分无效并在插入事务后重新获取适当数据的各种选项。根据到目前为止的经验,有一些潜在的策略:

a)调用refetch返回的useQuery方法以强制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页面,则效果很好。

我的主要问题(最终!)

这导致了我不确定的难题的主要部分:

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

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

我的想法是,这将使每个组件变得更加“活跃”-组件只需知道它们所依赖的数据,并且只要底层缓存图中的数据丢失,它就可以通过自身触发触发立即做出反应。查询。对于组件来说,最好以声明方式对它们所依赖的数据变化做出反应,而不是在必要时强制性地相互进行触发操作。

1 个答案:

答案 0 :(得分: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 作为您的实现的后端。这将使您可以更好地控制所需的缓存。但是,这需要大量的前期工程成本。

结论:

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