Соединяем Redux и GraphQL на простом примере

от автора

Попробуем соединить Redux и GraphQL без использования Apollo Client или Relay.

Что такое Redux

Редакс это архитектурный подход, который реализован в виде небольшой библиотеки. Он предполагает, что у приложения есть хранилище данных state. Изменять хранилище state напрямую нельзя. Это можно сделать только через вспомогательные функции reducers.

Псевдокод:

function dispatch(action) {     state = reducer(state, action); }

Для соединения реакта и редакса используется библиотека react-redux. Она позволяет получать данные из state внутри компонентов и инициировать их обновление через dispatch.

Что такое GraphQL

GraphQL для клиента это просто POST-запрос с особым форматом body:

REST

GraphQL

GET /books/id

POST /graphql
query{ book(id) {} }

POST /books
{…}

POST /graphql
mutation{ addBook() {...} }

UPDATE /books/id
{…}

POST /graphql
mutation{ updateBook(id) {...} }

DELETE /books/id

POST /graphql
mutation{ deleteBook(id) {} }

GraphQL без Apollo Client (и Relay)

Apollo-client это самая популярная библиотека для работы с GraphQL. Она предоставляет интерфейс для работы с запросами внутри компонентов, а также решает задачу хранения кэша. Вместо Redux предполагается использовать внутренние механизмы. Однако эти механизмы далеко не такие простые и удобные.

Вместо Apollo Client для примера воспользуемся такой функцией:

const graphqlAPI = (query, variables) => fetch("/graphql", {     method: "POST",     body: JSON.stringify({ query, variables }) });

redux-toolkit

Я буду использовать официальный набор утилит от redux — redux-toolkit. Они решают самую частую претензию к редаксу — бесконечное количество шаблонного кода. В этот набор входит:

  • createEntityAdapter для упрощения работы с редьюсерами и селекторами.

  • createAsyncThunk для упрощения работы с сайд-эффектами и API.

  • createSlice для упрощения работы с actions и разрешения мутабельности.

Пример

Представим простое приложение, которое запрашивает список книг с сервера и выводит их.

// booksSlice.js import {   createEntityAdapter,   createAsyncThunk,   createSlice } from "@reduxjs/toolkit";  // Наш редьюсер будет хранить список книг.  // createEntityAdapter генерирует готовый набор функций // для добавления, обновления и удаления книг внутри редьюсера // (addOne, addMany, updateOne, removeOne, setAll, ...),  const books = createEntityAdapter();  // createAsyncThunk позволяет работать с асинхронным кодом. // Например, с запросами к API. // getBooks работает как набор actions, которые можно диспатчить в приложении. export const getBooks = createAsyncThunk("get books", async () =>    await graphqlAPI(`     query {       books  {         id         title       }     }   `) );  export const addBook = createAsyncThunk("add book", ({ title }) =>   graphqlAPI(`     mutation ($title: string!){       add_book(objects: { title: $title }) {         id         title       }     }   `, { title }) );  // createSlice — обертка над createAction и createReducer. // Она позволяет работать со стейтом мутабильно. export const booksSlice = createSlice({   name: "books",   initialState: books.getInitialState(),   extraReducers: {     // Работаем с результатом запроса getBooks     [getBooks.fulfilled]: (state, action) => {       // setAll автоматически создан в createEntityAdapter       books.setAll(state, action.payload);     },     // Работаем с результатом запроса addBook     [addBook.fulfilled]: (state, action) => {       // addOne автоматически создан в createEntityAdapter       books.addOne(state, action.payload);     },   }, });  // createEntityAdapter генерирует набор готовых селекторов // (selectIds, selectById, selectAll, ...) export const booksSelectors = books.getSelectors((s) => s.books);  export default booksSlice.reducer;
// index.js import { useDispatch, useSelector } from "react-redux"; import { getBooks, addBook, booksSelectors } from "./booksSlice";  export function Books() {   const dispatch = useDispatch();    // booksSelectors.selectAll втоматически создан через createEntityAdapter   const books = useSelector(booksSelectors.selectAll);     useEffect(() => {     dispatch(getBooks());   }, [dispatch]);    return (     <div>       <button onClick={() => dispatch(addBook({ title: "New Book" }))}>         Add book       </button>       <ul>         {books.map((b) => <li key={b.id}>{b.title}</li>)}       </ul>     </div>   ); }

Что дальше

Поэкспериментировать с Redux можно локально.
Для настройки окружения достаточно одной команды:
npx create-react-app my-app --template redux

Для Typescript:
npx create-react-app my-app --template redux-typescript

Вместо fetch можно воспользоваться библиотекой graphql-request.

Создать бэкенд на GraphQL можно без написания кода.
Для этого подходят такие проекты как hasura или prisma.

ссылка на оригинал статьи https://habr.com/ru/post/524000/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *