
Введение в Apollo GraphQL с помощью React Hooks и Context API
Ранее я писал о том, как использовать Angular с Apollo Graph QL здесь. Я смотрел видео о GraphQL на сайте egghead.io от Eve Porcello и подумал, что было бы интересно имитировать действия, которые она выполняла на игровой площадке GraphQL (https://pet-library.moonhighway.com ) С использованием Angular и Apollo GraphQL Client.
Поскольку я давно хотел попробовать React, я подумал о том, чтобы переписать приложение angular в React, используя хуки и функциональные компоненты, и хотел бы поделиться с вами своим опытом обучения.
Проект
Наш проект будет простым клоном игровой площадки с библиотекой домашних животных с нуля с использованием React и Apollo Client, с функциями, включая аутентификацию, возврат списка всех домашних животных и домашних животных, регистрируемых / выписываемых. Мы не будем использовать компоненты на основе классов в этом проекте, поскольку перехватчики реакции позволяют нам управлять состоянием локального компонента и жизненным циклом компонента.
Вы можете просмотреть полный исходный код здесь.
Структура папки

На изображении выше показана структура папок, которую мы будем использовать. Все повторно используемые компоненты будут помещены в папку компонентов, страницы, которые будут представлять страницу или маршрут, будут помещены в папку страниц. Хуки, которые используются компонентами, попадают в папку хуков. Контексты React, к которым будут обращаться компоненты, будут помещены в папку провайдеров.
Папка Components будет включать в себя следующие компоненты:
- List.js: простой презентационный компонент, который отображает список домашних животных и принимает домашних животных в качестве реквизита.
- CheckIn.js: компонент для регистрации питомца, а также для обновления списка.
- CheckOut.js: компонент для проверки питомца, компонент CheckIn и CheckOut принимает идентификатор питомца в качестве опоры.
- SelectStatus.js: компонент для фильтрации домашних животных по статусу.
Папка Pages будет включать в себя следующие компоненты:
- ListContainer.js: компонент контейнера, который будет содержать как раскрывающийся список фильтра, так и компонент списка.
- Login.js: компонент, который занимается аутентификацией.
Давайте воспользуемся приложением create-react-app для создания нашего проекта.
create-react-app react-apollo-graphql-hooks
В нашем проекте мы также будем использовать реактивную загрузку, поэтому давайте добавим ее в наш проект, набрав:
npm install react-bootstrap bootstrap
Затем мы создадим SelectStatus.js в папке компонентов и добавим следующий код.
export const SelectStatus = ({ petStatus, defaultValue, onSelect }) => {
const setSelect = (e) => {
e.preventDefault();
let index = e.target.options.selectedIndex;
let status = petStatus[index];
if (onSelect) {
onSelect(status);
}
};
return (
<>
<Form.Group controlId="status">
<Col>
<Form.Label>Pet Status:</Form.Label>
</Col>
<Col>
<Form.Control
as="select"
defaultValue={defaultValue?.name}
onChange={(e) => setSelect(e)}
>
{petStatus.map((item) => {
return <option key={item.key}>{item.name}</option>;
})}
</Form.Control>
</Col>
</Form.Group>
</>
);
};
Пока не обращайте внимания на свойства {petStatus, defaultValue, onSelect}, мы вернемся к ним позже. Как видите, этот компонент - всего лишь презентационный компонент, который не хранит никакого внутреннего состояния и просто отображает компонент начальной загрузки «select», просматривая список статусов питомца.
Перейдем к компоненту списка.
export const List = ({ pets }) => {
return (
<>
<div className="row mt-4">
<div className="col-sm-8">
<table className="table table-striped">
<thead>
<tr>
<td className="w-25">
<p> Pet </p>
</td>
<td className="w-30">
<p> Category</p>
</td>
<td className="w-50">
<p> Customer</p>
</td>
<td className="w-50">
<p> Action</p>
</td>
</tr>
</thead>
<tbody>
{pets.map((item) => {
return (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.category}</td>
<td>{item.inCareOf?.name}</td>
<td>
{item.status === "AVAILABLE" ? (
<CheckOut petId={item.id} />
) : (
<CheckIn petId={item.id} />
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
;
</>
);
};
Это также просто презентационный компонент, который отображает список домашних животных. Он также имеет компонент CheckIn / CheckOut, который принимает идентификатор питомца в качестве опоры. Вернемся к компонентам CheckIn / CheckOut позже.
Прежде чем создавать компоненты контейнера, давайте напишем нашу первую ловушку Apollo Client. Создайте usePetsHooks.js в папке хуков с помощью следующего кода.
import { useQuery } from "@apollo/client";
import gql from "graphql-tag";
const petFieldsQuery = gql`
fragment petFields on Pet {
id
name
category
status
inCareOf {
name
}
}
`;
export const filterPetsQuery = gql`
query petsQuery($status: PetStatus) {
allPets(status: $status) {
...petFields
}
}
${petFieldsQuery}
`;
export default (status) => {
return useQuery(filterPetsQuery, {
fetchPolicy: "network-only",
variables: {
status: status,
},
});
};
Мы используем хук useQuery Apollo Client для получения данных GraphQL. Вызов useQuery возвращает объект со свойствами, включая загрузку, ошибку, данные и функцию повторной выборки. Мы рассмотрим, как использовать функцию refetch, когда перейдем к функциям CheckIn / CheckOut. Я также сохраняю fetchPolicy как «только для сети», поскольку мы не заинтересованы в кэшировании результатов запросов в этом проекте.
Нам также необходимо убедиться, что список обновляется, когда мы выполняем CheckIn / CheckOut, чтобы текущий питомец исчез из списка. Помните, что useQuery возвращает функцию повторной выборки? И мы хотим вызвать эту функцию refetch из компонента CheckIn, когда мы регистрируем питомца. Как это сделать, не делая компонент List сквозным компонентом для повторной выборки?
Один из способов - использовать Context API, поэтому нам не нужно вручную передавать реквизиты через компонент List в компоненты CheckIn / CheckOut. Итак, используя API, мы можем создать новый контекст. Создайте файл refetchProvider.js со следующим кодом.
import React from "react";
export const PetsContext = React.createContext({});
export const PetsProvider = PetsContext.Provider;
Провайдер может содержать любые значения, а также может быть функцией (действием). В следующем разделе мы установим функцию refetch в качестве значения поставщика.
Затем давайте создадим компонент-контейнер ListContainer.js.
export const ListContainer = () => {
const petStatus = [
{ key: 1, id: null, name: "All" },
{ key: 2, id: "AVAILABLE", name: "Available" },
{ key: 3, id: "CHECKEDOUT", name: "Checked Out" },
];
const [selectedStatus, setSelectedStatus] = useState(() => null);
const { loading, error, data, refetch } = usePetsQuery(
selectedStatus ? selectedStatus.id : null
);
const onSelectStatus = (status) => {
setSelectedStatus(status);
};
const onRefetch = () => {
refetch();
};
if (loading) return "Loading...";
if (error) return `Error! ${error.message}`;
return (
<>
<Container className="mt-4">
<Form>
<Form.Row>
<SelectStatus
petStatus={petStatus}
onSelect={onSelectStatus}
defaultValue={selectedStatus}
/>
<div className="ml-auto">
<Logout />
</div>
</Form.Row>
</Form>
<PetsProvider value={() => onRefetch()}>
<List pets={data.allPets} />
</PetsProvider>
</Container>
</>
);
};
Мы используем шаблон «Контейнер», чтобы отделить состояние и события от компонентов представления.
const [selectedStatus, setSelectedStatus] = useState(() => null);
Здесь мы используем React.useState для поддержания состояния раскрывающегося списка выбора. useState возвращает массив, и мы можем использовать синтаксис деструктуризации ES6 для доступа к значениям. Когда мы меняем фильтр выбора, нам нужно повторно визуализировать весь компонент списка, и функция обновления (setSelectedStatus) позаботится об этом.
Также обратите внимание, как мы обернули компонент List в PetsProvider. Это помогает нам использовать контекст в каждом компоненте. Мы скоро увидим это в компоненте CheckIn.
Для функциональности регистрации давайте создадим компонент CheckIn.
export const CheckIn = ({ petId }) => {
const refetch = useContext(PetsContext);
const doCheckIn = useCheckInMutation();
const checkIn = () => {
doCheckIn(
{
variables: { petId: petId },
},
{ refetchQueries: [`petsQuery`] }
)
.then((_) => {
refetch();
})
.catch((e) => console.log(e));
};
if (!isLoggedIn()) {
return null;
}
return (
<>
<button onClick={() => checkIn()} className="btn btn-link">
Check In
</button>
</>
);
};
Мы получаем ссылку на обработчик refetch из useContext API. После того, как произойдет проверка мутации, мы вызовем функцию refetch (), которая, в свою очередь, вызовет обработчик onRefetch в ListContainer.js.
Вывод
Это была моя попытка поделиться тем, что я узнал с помощью перехватчиков React и Context API. В этом примере проекта показано, как поддерживать локальное состояние с помощью useState и как передавать контекст внутренним компонентам, если они находятся где-то в одном дереве компонентов. Более подробную информацию о хуках можно найти здесь.
Вы можете просмотреть полный исходный код здесь.
Примечание из JavaScript In Plain English
Мы запустили три новых издания! Проявите любовь к нашим новым публикациям, подписавшись на них: AI на простом английском, UX на простом английском, Python на простом английском - спасибо и продолжайте учиться!
Мы также всегда заинтересованы в продвижении качественного контента. Если у вас есть статья, которую вы хотели бы отправить в какую-либо из наших публикаций, отправьте нам электронное письмо по адресу [email protected] с вашим именем пользователя Medium, и мы добавим вас в качестве автора. Также сообщите нам, к каким публикациям вы хотите быть добавлены.