有没有办法检测查询使用的数据何时从 Apollo 客户端缓存中逐出?

Mat*_*son 15 caching apollo reactjs apollo-client

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

背景

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

  • 有一个Account组件(下面的示例)使用useQueryreact-apollo 中的钩子来获取和显示有关帐户的一些基本信息以及该帐户的交易列表
  • 在应用程序的其他地方,有一个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>
  );
};
Run Code Online (Sandbox Code Playgroud)

潜在策略

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

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

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

b) 将查询名称 ( ) 传递getAccountWithTransactionsrefetchQueries变异选项中

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

c) 执行mutation后手动修改缓存内容

  • 我想这会非常复杂/难以维护,因为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();
Run Code Online (Sandbox Code Playgroud)

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

我的主要问题(终于!)

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

有什么方法可以检测查询中引用的部分或全部数据是否已从缓存中逐出?

我不确定这是否是库所期望的可行的事情,但如果可能的话,我认为它可能会导致更简单的代码和应用程​​序不同部分之间的更少耦合。

我的想法是,这将使每个组件变得更具“反应性”——组件只知道它们依赖哪些数据,并且每当底层缓存图中的数据丢失时,它可以通过触发对自己的查询的重新获取来立即做出反应。如果组件对它们所依赖的数据的变化做出声明性的反应,而不是强制地进行通信以触发对彼此的操作,那会很好。

Jac*_*ack 1

这里有两个答案,执行此操作的标准实践方法是什么?我们怎样才能创造出更好的方法呢?

标准实践

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

Apollo 在他们的博客和文档中指出,如果您的突变在返回的查询之外产生副作用,那么如果您想保持缓存一致性,则必须使用更新函数。这与 using 类似,refetchQueries但更明确,因为您自己更新缓存(它也需要更少的网络请求)。尽管如此,您是对的,这种方法将导致交易的突变更新与所有其他依赖组件之间的耦合。

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

更好的方法

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

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

结论:

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