Представьте помощника с записной книжкой. Вы просите его узнать список постов: он идёт на сервер, приносит ответ и записывает в книжку — не как попало, а по «карточкам». У каждого поста своя карточка Post:1, у каждого автора — User:7. В следующий раз помощник отвечает из книжки, а когда карточка меняется, сам сообщает всем экранам, где она показана. Таков Apollo Client: он ведёт запросы GraphQL, состояния загрузки и ошибок, кеш и обновление интерфейса. В этом уроке вы подключите его к React-клиенту блога, выведете список постов, добавите форму, научитесь обновлять кеш после мутаций и отправлять JWT.

Что даёт GraphQL-клиент

Можно обойтись обычным fetch, как в первом уроке, но придётся вручную вести loading и error, не дублировать запросы и следить, чтобы после мутации все экраны показали новые данные. Клиент берёт это на себя. Сравним с тем, что вы знаете:

  • Apollo Client понимает GraphQL и хранит данные нормализованно: по объектам, а не по запросам. Самый популярный выбор.
  • TanStack Query (тема про React) кеширует ответы по ключам запросов и подходит для любого API, но объектов GraphQL «не знает».
  • urql — лёгкий клиент: по умолчанию кеширует документы целиком, а нормализованный кеш подключается отдельным пакетом.

Подключаем Apollo Client

Создайте проект на Vite и поставьте пакеты. Для Apollo Client 4 нужен ещё rxjs. Адрес сервера лежит в .env: Vite отдаёт браузеру только переменные с префиксом VITE_.

bash
npm create vite@latest blog-web -- --template react-ts
cd blog-web
npm install @apollo/client graphql rxjs
# then create .env with the line: VITE_GRAPHQL_URL=http://localhost:4000/graphql

Ссылку на сервер в версии 4 создают явно (new HttpLink), а хуки и ApolloProvider берут из @apollo/client/react. Клиент создают один раз и оборачивают им приложение:

tsx
// src/apollo.ts
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';

export const client = new ApolloClient({
  link: new HttpLink({ uri: import.meta.env.VITE_GRAPHQL_URL }),
  cache: new InMemoryCache(),
});

// src/main.tsx
import { ApolloProvider } from '@apollo/client/react';
import { client } from './apollo.ts';

createRoot(document.getElementById('root')!).render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
);

useQuery: список постов

Запрос пишут в шаблонной строке gql. Хук useQuery отправляет его при первом показе компонента и возвращает состояние:

tsx
// src/PostList.tsx
import { gql } from '@apollo/client';
import { useQuery } from '@apollo/client/react';

type Post = { id: string; title: string; author: { name: string } };

const POSTS = gql`
  query Posts($published: Boolean) {
    posts(published: $published) {
      id
      title
      author { id name }
    }
  }
`;

export function PostList() {
  const { data, loading, error, refetch } = useQuery<{ posts: Post[] }>(
    POSTS,
    { variables: { published: true } },
  );

  if (loading && !data) return <p>Loading…</p>;
  if (error) return <p>Error: {error.message}</p>;