Введение
В React мы очень любим хуки. Почему? Потому что они позволяют нам писать код, который легко читается, переиспользуется и тестируется. Apollo Client поддерживает эту философию, предоставляя хуки useQuery и useMutation для удобной работы с GraphQL-запросами и мутациями. Это делает их использование естественным для любого React-разработчика.
Сегодня мы будем:
- Узнавать, как
useQueryпомогает нам запрашивать данные. - Использовать
useMutationдля изменения данных на сервере. - Обсуждать обработку состояний загрузки, ошибок и успешного получения данных.
- Рассматривать примеры и строить приложение, которое активно использует GraphQL.
Ну что, готовы? Сейчас начнется самое интересное.
Хук useQuery: запрашиваем данные
Хук useQuery позволяет вам отправлять запросы на сервер GraphQL и получать данные. Всё просто: вы передаете ему GraphQL-запрос, а в ответ получаете объект со статусами загрузки, ошибками и данными.
Пример:
import { gql, useQuery } from '@apollo/client';
// Определяем GraphQL-запрос
const GET_USERS = gql`
query GetUsers {
users {
id
name
email
}
}
`;
const UsersList: React.FC = () => {
// Используем хук useQuery
const { loading, error, data } = useQuery(GET_USERS);
if (loading) return <p>Загрузка...</p>;
if (error) return <p>Ошибка: {error.message}</p>;
return (
<ul>
{data.users.map((user: { id: string; name: string; email: string }) => (
<li key={user.id}>
{user.name} ({user.email})
</li>
))}
</ul>
);
};
export default UsersList;
Как это работает?
- Создаём запрос через
gql: сначала описываем GraphQL-запрос (в данном случаеGET_USERS). - Используем
useQuery: передаем запрос в хук, и он автоматически отправляет его на сервер. - Обрабатываем состояния:
loading: когда данные загружаются.error: когда что-то пошло не так.data: когда данные успешно загружены.
Типизация useQuery с TypeScript
Хороший код — это типизированный код. Давайте научимся типизировать результаты запроса.
Шаг 1: Создаём интерфейсы
interface User {
id: string;
name: string;
email: string;
}
interface UsersData {
users: User[];
}
Шаг 2: Передаём типы в useQuery
const { loading, error, data } = useQuery<UsersData>(GET_USERS);
Теперь TypeScript подскажет нам, какие свойства доступны в data, что значительно снижает риск ошибок.
Хук useMutation: изменяем данные
Хук useMutation нужен для выполнения мутаций. Если useQuery — это "читать данные", то useMutation — это "менять данные" (добавлять, удалять, обновлять).
Пример:
import { gql, useMutation } from '@apollo/client';
// Определяем мутацию
const ADD_USER = gql`
mutation AddUser($name: String!, $email: String!) {
addUser(name: $name, email: $email) {
id
name
email
}
}
`;
const AddUserForm: React.FC = () => {
const [addUser, { data, loading, error }] = useMutation(ADD_USER);
const handleSubmit = async () => {
try {
const result = await addUser({ variables: { name: 'John', email: 'john@doe.com' } });
console.log('Добавлен пользователь:', result.data.addUser);
} catch (e) {
console.error('Ошибка:', e);
}
};
return (
<div>
{loading && <p>Добавление пользователя...</p>}
{error && <p>Ошибка: {error.message}</p>}
<button onClick={handleSubmit}>Добавить пользователя</button>
</div>
);
};
export default AddUserForm;
Как это работает?
- Определяем мутацию: описание GraphQL-запроса
ADD_USERдля добавления пользователя. - Получаем функцию
addUserчерезuseMutation: она отправляет запрос на сервер. - Передаём переменные: используем параметр
variablesдля передачи данных в сервер.
Типизация useMutation с TypeScript
Вы наверняка догадались: типы тут тоже спасают нас от бед.
Шаг 1: Создаём интерфейсы
interface AddUserVariables {
name: string;
email: string;
}
interface AddUserResponse {
addUser: {
id: string;
name: string;
email: string;
};
}
Шаг 2: Передаём типы в useMutation
const [addUser] = useMutation<AddUserResponse, AddUserVariables>(ADD_USER);
Теперь TypeScript проверяет и переменные, и структуру ответа!
Обработка ошибок и оптимизация
Работа с GraphQL не всегда проходит гладко. Вот несколько советов:
Обрабатывайте ошибки в хуках. Используйте
errorизuseQueryиuseMutation, чтобы показать пользователю, если что-то пошло не так.Используйте состояние загрузки. Показывайте индикаторы загрузки
loading, чтобы интерфейс оставался отзывчивым.Оптимизируйте кэш. После выполнения мутаций обновляйте кэш, чтобы изменения сразу отобразились в UI:
const [addUser] = useMutation(ADD_USER, { update(cache, { data }) { const existing = cache.readQuery<UsersData>({ query: GET_USERS }); if (existing && data) { cache.writeQuery({ query: GET_USERS, data: { users: [...existing.users, data.addUser], }, }); } }, });
Создаём форму для добавления пользователей
Давайте свяжем useQuery и useMutation в одно приложение.
Код:
const App: React.FC = () => {
return (
<ApolloProvider client={client}>
<h1>Список пользователей</h1>
<UsersList />
<h2>Добавить пользователя</h2>
<AddUserForm />
</ApolloProvider>
);
};
Теперь наше приложение умеет как получать данные с помощью useQuery, так и изменять их с помощью useMutation.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ