Введение в 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, и мы добавим вас в качестве автора. Также сообщите нам, к каким публикациям вы хотите быть добавлены.