Извиняюсь за длинный вопрос, но я был бы очень признателен за некоторые мысли / помощь по лучшей стратегии для аннулирования кэша и повторного запроса запросов в Apollo Client 3.
Фон
Первый Немного информации о сценарии, который я себе представляю:
- Существует компонент
Account
(пример ниже), который использует хук useQuery
из Reaction-Apollo для получения и отображения некоторых баз c информация об учетной записи и список транзакций для этой учетной записи - В другом месте приложения есть компонент
CreateTransactionForm
, который использует мутацию для вставки новой транзакции. Это отдельный компонент, который находится в другом месте в дереве компонентов и не обязательно является дочерним по отношению к AccountComponent
) - . Важно отметить, что процесс хранения транзакции на сервере имеет некоторую нетривиальную сторону эффекты помимо вставки фактической транзакции в базу данных:
- все остальные транзакции, которые происходят после вставляемая (в хронологическом порядке), обновляются с новыми текущими балансами
- любой связанный счет обновлены с новым текущим балансом
Упрощенная c версия моего 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) вызвать метод refetch
, возвращаемый useQuery
, чтобы заставить Account
компонент перезагрузить свои данные
- это кажется надежным и будет go возвращаться на сервер для данных fre sh, но
CreateTransactionForm
должен быть (прямо или косвенно) связан с компонентом Account
, потому что что-то должно сработать при вызове refetch
b) передать имя запроса (getAccountWithTransactions
) в параметр refetchQueries
мутации
- , аналогичный a, но с потенциально даже более тесная связь -
CreateTransactionForm
должен был бы знать обо всех других компонентах / запросах, которые существуют в приложении и могут быть затронуты мутацией (и если в будущем будет добавлено еще больше, это будет означать, что нужно обновить CreateTransactionForm
)
c) вручную изменить содержимое кэша после выполнения мутаций
- Я думаю, что это будет довольно сложно / трудно поддерживать, потому что
CreateTransactionForm
будет Чтобы узнать, какие именно данные изменились в результате действий сервера. Как уже упоминалось, это может быть не тривиальный объем данных, и после выполнения мутации нам потребуется извлечь обновленные данные не только о транзакции, которая была вставлена, но также и о любых других, которые были обновлены как побочный эффект, плюс затронутые учетные записи, и др c. Это также может быть не очень эффективно, потому что часть этой информации никогда не будет снова отображаться на клиенте.
Моя интуиция заключается в том, что ни один из описанных выше вариантов не кажется идеальным. В частности, меня беспокоит возможность сопровождения по мере роста приложения; если компоненты должны иметь четкие знания о том, какие именно компоненты / запросы могут быть затронуты изменениями, внесенными в граф данных, то возникает ощущение, что будет очень легко пропустить один и внести незначительные ошибки, когда приложение станет больше и больше complex.
Лучший способ?
Я очень заинтересован в новых методах evict
и gc
, представленных в Apollo Client 3, и мне интересно, могут ли они обеспечить более точное решение.
Что я думаю, так это то, что после вызова мутации я мог бы использовать эти новые возможности для:
- агрессивного удаления массива
transactions
на всех учетных записях, которые включены в транзакцию - также, исключите поле
currentBalance
на любых затронутых учетных записях
, например:
const { cache } = useApolloClient();
...
// after calling the mutation:
cache.evict(`Account:${accountId}`, 'transactions');
cache.evict(`Account:${accountId}`, 'currentBalance');
cache.gc();
Вышеприведенное обеспечивает простой способ удаления устаревших данных из кэша и гарантирует, что компоненты будут go подключаться к сети при следующем выполнении запросов этих полей. Это хорошо работает, если я перейду к другой странице и вернусь, например, к странице Account
.
Мой главный вопрос (наконец-то!)
Это приводит к основной части головоломка, в которой я не уверен:
есть ли способ обнаружить, что некоторые или все данные, на которые есть ссылки в запросе, были удалены из кэша?
Я не уверен, что от библиотеки этого можно ожидать, но если это возможно, я думаю, что это может привести к упрощению кода и уменьшению связи между различными частями приложения.
Я думаю, что это позволит каждому компоненту стать более «реактивным» - компоненты просто знают, от каких данных они зависят, и всякий раз, когда эти данные пропадают из основного кэшированного графа, он может немедленно реагировать, вызывая повторное получение по своему собственному запросу. Было бы неплохо, чтобы компоненты декларативно реагировали на изменения в данных, от которых они зависят, вместо того, чтобы обязательно сообщать о инициировании действий друг на друге, если это имеет смысл.