Архитектура кэша Apollo
Давайте представим, что вы смотрите сериал на стриминговой платформе. Вы уже открыли первую серию и начали просмотр. Если вы зайдете на страницу той же серии повторно, платформа не станет снова загружать все данные о ней с сервера. Вместо этого она возьмет информацию из локального хранилища или кэша. Это происходит быстрее и уменьшает нагрузку на сервер.
То же самое касается GraphQL и Apollo Client: правильное использование кэша позволяет вашему приложению работать быстрее и отзывчивее, избегая избыточных запросов к серверу.
В Apollo Client используется нормализованный кэш. Это значит, что данные хранятся в виде объектов, где каждому объекту присваивается уникальный идентификатор. Таким образом, Apollo может обновлять только те части кэша, которые действительно изменились, вместо полного изменения данных.
Пример структуры кэша:
{
"ROOT_QUERY": {
"user:1": {
"__ref": "User:1"
},
"user:2": {
"__ref": "User:2"
}
},
"User:1": {
"id": "1",
"name": "Alice",
"age": 25
},
"User:2": {
"id": "2",
"name": "Bob",
"age": 30
}
}
Политики кэширования
Политики кэширования определяют, как Apollo Client будет использовать локальный кэш и серверные данные. Они задаются для каждого запроса и помогают вам эффективно управлять состоянием.
cache-first: сначала пытается взять данные из кэша. Если данных нет — выполняет запрос к серверу.
Подходит для данных, которые редко меняются, например, список статей.network-only: всегда запрашивает данные с сервера, игнорируя кэш.
Подходит для часто обновляющихся данных, например, уведомлений.cache-only: ищет данные только в кэше. Этот вариант отключает запросы к серверу.
Полезно, когда кэширование является единственным источником данных.no-cache: игнорирует кэш, данные хранятся только временно для одного исполнения запроса.
Полезно, когда данные для запроса не должны кэшироваться вовсе.
Управление кэшем
Apollo предоставляет удобные методы работы с кэшем, такие как cache.writeQuery и cache.readQuery. Эти методы позволяют напрямую взаимодействовать с хранимыми данными.
Давайте создадим приложение и посмотрим на эти методы в действии.
Часть 1: Настройка политики кэширования
Предположим, у нас есть сервер GraphQL с двумя запросами:
- Получение всех пользователей.
- Получение конкретного пользователя по
id.
Для этого настроим Apollo Client в нашем React-приложении:
import { ApolloClient, InMemoryCache, ApolloProvider } from "@apollo/client";
const client = new ApolloClient({
uri: "https://your-graphql-api.com/graphql",
cache: new InMemoryCache(),
});
function App() {
return (
<ApolloProvider client={client}>
<div>
<h1>Users App</h1>
{/* Другие компоненты */}
</div>
</ApolloProvider>
);
}
export default App;
Часть 2: Чтение данных из кэша
Допустим, мы уже сделали запрос на получение всех пользователей. Теперь нам нужно прочитать их из кэша.
Запрос:
query GetUsers {
users {
id
name
age
}
}
Код в React-компоненте:
import { useQuery, gql } from "@apollo/client";
const GET_USERS = gql`
query GetUsers {
users {
id
name
age
}
}
`;
function UsersList() {
const { data, loading, error } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h2>Users:</h2>
<ul>
{data.users.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
export default UsersList;
После выполнения запроса данные кешируются автоматически. Чтобы убедиться в этом, можно воспользоваться методом cache.readQuery.
import { useApolloClient } from "@apollo/client";
function ReadCache() {
const client = useApolloClient();
const readData = () => {
const cachedData = client.cache.readQuery({
query: GET_USERS,
});
console.log("Cached Data:", cachedData);
};
return <button onClick={readData}>Read Cache</button>;
}
export default ReadCache;
Часть 3: Обновление кэша после мутации
Теперь предположим, что мы добавляем нового пользователя с помощью мутации.
mutation AddUser($name: String!, $age: Int!) {
addUser(name: $name, age: $age) {
id
name
age
}
}
Обновление кэша после добавления нового пользователя:
import { gql, useMutation } from "@apollo/client";
const ADD_USER = gql`
mutation AddUser($name: String!, $age: Int!) {
addUser(name: $name, age: $age) {
id
name
age
}
}
`;
function AddUser() {
const [addUser] = useMutation(ADD_USER);
const handleAddUser = () => {
addUser({
variables: { name: "Charlie", age: 35 },
update(cache, { data }) {
const newUser = data.addUser;
const existingUsers = cache.readQuery({ query: GET_USERS });
cache.writeQuery({
query: GET_USERS,
data: {
users: [...existingUsers.users, newUser],
},
});
},
});
};
return <button onClick={handleAddUser}>Add User</button>;
}
export default AddUser;
Часть 4: Политики кэширования для мутаций
По умолчанию Apollo автоматически обновляет кэш для мутаций, но вы можете управлять этим с помощью refetchQueries или update.
Использование refetchQueries:
addUser({
variables: { name: "Charlie", age: 35 },
refetchQueries: [{ query: GET_USERS }],
});
Этот метод менее предпочтителен для частого использования, так как делает дополнительный запрос к серверу.
Закономерные ошибки
Одной из самых частых ошибок является забывчивость относительно структуры кэша. Если вы пытаетесь обновить кэш, вы должны быть уверены, что правильно указываете структуру данных, иначе Apollo может создать дубликаты или вовсе не обновить кэш.
Также следите за идентификаторами объектов в кэше — если они указаны неправильно, это может привести к проблемам синхронизации данных. Пример: не указать id или другой уникальный ключ.
Теперь ваш GraphQL-клиент не только умнее, но и работает с данными быстрее, чем вы успеваете сказать "overfetching"!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ