Улучшите свой сервисный уровень в своих проектах Redux-toolkit

Как создать приложение React с помощью RTK-QUERY

Руководство для начинающих по написанию запросов с помощью rtk-query

Невозможно быть фронтенд-разработчиком, работающим с React, и не слышать о редуксе в своей жизни. Большинство компаний используют Redux в своей кодовой базе, и знание его основных концепций стало одной из основ работы Frontender. Существует много дискуссий о том, использовать или не использовать его в своем продукте. Правда в том, что вы никогда не сможете найти идеальный инструмент для всех проектов. Вы должны решить, какие инструменты лучше всего подходят для ваших целей и продукта. Несмотря на то, что у каждого инструмента есть свои плюсы и минусы, наиболее важным является компромисс между преимуществами и недостатками инструмента для ВАС.

RTK Query – это мощный инструмент для извлечения и кэширования данных. Он предназначен для упрощения распространенных случаев загрузки данных в веб-приложение, избавляя от необходимости вручную писать логику выборки и кэширования данных.

Если вы используете redux, особенно redux-toolkit, вы уже знаете, что rtk-query поставляется с ним как встроенная зависимость. Есть много подобных инструментов, таких как react-query, но если вы используете redux-toolkit и уже имеете rtk-query в своем комплекте, почему бы не использовать его? Некоторые преимущества trk-query:

  • Он полностью совместим с Redux
  • Написано на TypeScript
  • Улучшает ваш сервисный уровень, чтобы его можно было повторно использовать, и выводит ваш проект с автоматически сгенерированными хуками на новый уровень.
  • Его можно отслеживать и наблюдать с помощью redux-devtools.
  • У него действительно хорошее поведение кэширования

В этой статье я помогу вам создать простое приложение React для блога с помощью rtk-запроса и машинописного текста, чтобы вы лучше познакомились с этим замечательным инструментом. Это приложение состоит из представления для отображения списка сообщений и простой формы для добавления сообщения.

Предпосылка

  1. Вы должны знать React
  2. Вы должны знать концепции redux и redux-toolkit и хотеть использовать их в своем проекте.
  3. Наличие опыта работы с TypeScript является плюсом, но вы можете просто удалить части TS из кода, и он будет работать с JavaScript для вас :)

Вы можете найти репозиторий и все коды, которые я упоминаю в этой статье, здесь. Живую версию финального проекта можно найти здесь. Я также пропускаю объяснения стилей или простых частей JSX, так как основная тема здесь — rtk-query.

Как это работает?

Как я упоминал ранее, rtk-query — это инструмент выборки, который предоставляет множество функций, чтобы сделать ваш проект более чистым и пригодным для повторного использования. Ваши службы могут быть созданы в форме конечных точек и запросов с помощью createApi, который полностью аналогичен createReducer. Он генерирует хуки для ваших запросов, и вы можете легко использовать их в своих компонентах. Вам больше не понадобятся состояния загрузки, обработка ошибок, функции повторной загрузки, предварительная выборка с помощью useEffect и парсеры данных, потому что rtk-query позаботится об этом за вас.

На самом деле запросы — это наименьшие части ваших сервисов, которые могут быть GET или POST для получения или отправки данных на сервер. Все запросы, связанные с сущностью, упаковываются в одну конечную точку, созданную с помощью createApi. В конце концов, ваш сервисный уровень представляет собой комбинацию различных конечных точек, включая их собственные запросы.

rtk-query использует API выборки из JavaScript. Но вы можете настроить его на использование axios или graphql. Кроме того, он оборачивает ваши запросы методом immer, поэтому изменение данных больше не вызывает беспокойства.

Создание фундамента

Вы можете выбрать ЛЮБЫЕ инструменты и настройки для этого проекта. Я буду использовать Vite, но не имеет значения, нравится ли вам больше CRA или вы предпочитаете настраивать свой собственный шаблон. После создания нашего проекта React мы должны установить redux-toolkit + axios и настроить его:

npm install @reduxjs/toolkit react-redux axios

Во-первых, мы должны создать нашу базовую функцию запроса, чтобы настроить rtk-query для использования axios:

// src/services/api.ts

const axiosBaseQuery =
  (): BaseQueryFn<AxiosRequestConfig, unknown, AxiosError> =>
  async ({ url, method, data, params }) => {
    try {
      Axios.defaults.baseURL = "https://jsonplaceholder.typicode.com/";
      const result = await Axios({
        url,
        method,
        data,
        params,
      });
      return { data: result.data };
    } catch (axiosError) {
      const error = axiosError as AxiosError;
      return {
        error,
      };
    }
  };

BaseQueryFn — это промис, который должен разрешить наш запрос с некоторыми данными или отклонить его с объектом ошибки. Здесь мы получаем конфигурации простого запроса axios из наших запросов и выполняем выборку API с помощью axios. Затем мы вернем данные, если все было хорошо. В противном случае мы отклоняем его с помощью объекта ошибки. Обязательно возвращайте данные или ошибки в объекте, иначе rtk-query их не распознает.

В этом проекте мы будем использовать бесплатные API JSONPlaceholder.

Во-вторых, мы должны сообщить rtk-query, чтобы он использовал нашу пользовательскую функцию запроса вместо API-интерфейса JS. Это легко сделать, передав нашу функцию в createApi.

// src/services/api.ts

export const apiService = createApi({
  baseQuery: axiosBaseQuery(),
  endpoints: () => ({}),
});

Теперь мы можем просто добавить наши запросы к конечным точкам. Но, как видите, мы вернули пустой объект. Причина в том, что в реальном мире мы не хотим, чтобы все наши сервисы находились в одном длинном запутанном файле. Мы хотим разделить их на несколько небольших модулей, чтобы иметь более чистую кодовую базу. Итак, мы возвращаем пустой объект и просто внедряем наши конечные точки в этот объект позже.

Наконец, нам нужно добавить apiService в хранилище избыточности и добавить хранилище в наше приложение:

// src/store/store.ts

export const store = configureStore({
  reducer: {
    [apiService.reducerPath]: apiService.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(apiService.middleware),
});
// src/main.tsx 

<Provider store={store}>
    <App />
 </Provider>

Почему? apiService также содержит автоматически сгенерированный редуктор фрагментов Redux и настраиваемое промежуточное ПО, которое управляет сроками действия подписки. Оба они должны быть добавлены в магазин Redux.

Создание нашего первого сервиса

Мы начинаем с конечной точки GET, чтобы получить список сообщений с сервера. Мы можем просто использовать API injectEndpoint из rtk-query, чтобы создать наши конечные точки и добавить их на наш базовый уровень службы.

// src/services/posts.ts

// Interface of JSONPlaceholder posts
export interface Post {
  id: number;
  userId: number;
  title: string;
  body: string;
}

export const postService = apiService.injectEndpoints({
  endpoints: (build) => ({
    // query<ResultType, QueryArg>
    getPosts: build.query<Post[], null>({
      query: () => ({ method: "GET", url: "posts" }),
    }),
  }),
});

// Auto-generated hooks
export const { useGetPostsQuery } = postService;

endpoints — это функция с одним обязательным аргументом — build — которая возвращает объект различных запросов. build — это утилита, помогающая создавать конечные точки с помощью различных конструкторов. build.query следует использовать для конечных точек GET и build.mutation. следует использовать для других конечных точек, таких как POST и PATCH. Это немного похоже на то, что у нас есть в graphql. Эти конструкторы получают объект с одним обязательным аргументом — query. query простыми словами — это функция, которую вы используете для получения API, и она должна возвращать ваши конфигурации запроса axios, такие как метод, параметры и URL-адрес.

Затем rtk-query создает хуки, чтобы вы могли легко получать эти запросы. В конструкторы запросов, такие как transformResponse, можно передать дополнительные параметры, которые мы не рассматриваем, поскольку они выходят за рамки этой статьи.

Воспользуемся нашим первым сервисом

Теперь пришло время воспользоваться нашим сервисом. rtk-query сгенерировал для нас хук useGetPostsQuery. Этот хук возвращает объект, состоящий из некоторых полезных значений для использования. Например, состояние загрузки, ошибка, возвращаемые данные и функция обновления.

  • Состояние загрузки: оно будет true, когда мы выполним этот запрос в нашем компоненте.
  • Объект ошибки: если наш запрос завершится ошибкой, будет возвращен объект ошибки, в противном случае он null.
  • data: наши возвращенные данные. Он равен null, пока наш запрос не выполнится успешно.
  • функция обновления: мы можем использовать эту функцию для повторного запуска запроса всякий раз, когда нам это нужно. Например, если мы хотим иметь кнопку «Попробовать еще раз» или кнопку «Обновить».
// src/components/PostList.tsx

const PostList: React.FC<unknown> = () => {
  const { data: posts, isLoading, error, refetch } = useGetPostsQuery(null);

  if (isLoading) {
    return <div>Loading posts...</div>;
  }

  if (error) {
    return <ErrorBanner error={error as AxiosError} refetch={refetch} />;
  }

  return (
    <div>
      <h2>Posts</h2>
      <div>
        {posts?.map((post) => (
          <div className="postItem">
            <h4>{post.title}</h4>
            <p>{post.body}</p>
          </div>
        ))}
      </div>
    </div>
  );
};

export default PostList;

Хук useQuery впервые получает наш API при монтировании компонента. Мы можем управлять им вручную, используя вместо этого useLazyQuery. Как видите, наш компонент стал намного чище, и мы не использовали в нем никаких useEffect или useState.

Давайте построим нашу форму

Чтобы двигаться дальше, давайте также рассмотрим один запрос на мутацию. Мы хотим создать сообщение, используя форму, которая получает заголовок и текст от пользователей. Начнем с самого запроса:

// src/services/posts.ts

export interface CreatePostDto {
  title: string;
  body: string;
}

export const postService = apiService.injectEndpoints({
  endpoints: (build) => ({
    // query<ResultType, QueryArg>
    getPosts: build.query<Post[], null>({
      query: () => ({ method: "GET", url: "posts" }),
    }),
    // We use mutation for POST endpoints
    createPost: build.mutation<Post, CreatePostDto>({
      query: (data) => ({
        method: "POST",
        url: "posts",
        // We pass static userId to simplify this part
        data: { userId: 1, ...data },
      }),
    }),
  }),
});

// Auto-generated hooks
export const { useGetPostsQuery, useCreatePostMutation } = postService;

По сравнению с конечной точкой GET у нас есть 2 отличия:

  1. Поскольку мы хотим добавить некоторые данные методом POST, мы используем build.mutation вместо build.query.
  2. Мы указали новый интерфейс для аргументов нашей конечной точки и передали соответствующие данные в наш запрос axios.

Теперь мы должны использовать этот новый сервис в нашем компоненте формы:

// src/components/PostForm.tsx

const PostForm: React.FC<unknown> = () => {
  const [title, setTitle] = useState<string>("");
  const [body, setBody] = useState<string>("");

  const [createPost, { isLoading }] = useCreatePostMutation();

  const submitForm = async (e: FormEvent) => {
    e.preventDefault();
    try {
      await createPost({ title, body });
    } catch (e) {
      console.log(e);
    }
  };

  return (
    <form onSubmit={submitForm}>
      <input
        name="title"
        type="text"
        placeholder="Title"
        value={title}
        onChange={(e) => setTitle(e.currentTarget.value)}
      />
      <textarea
        name="body"
        placeholder="Body..."
        rows={5}
        value={body}
        onChange={(e) => setBody(e.currentTarget.value)}
      />
      <input
        type="submit"
        value={isLoading ? "Wait..." : "Submit"}
        disabled={isLoading}
      />
    </form>
  );
};

export default PostForm;

Хук useMutation возвращает кортеж. Первый элемент этого массива — это API выборки, который вы можете вызывать с указанными аргументами. Второй элемент — это объект, который состоит из некоторых важных значений, таких как состояние загрузки. Он также возвращает объект ошибки, который мы можем использовать для отображения ошибки в случае сбоя отправки данных.

Заверните

Мы рассмотрели наиболее важные базовые понятия rtk-запроса. Мы увидели, как мы можем создавать наши сервисы и конечные точки по модульному принципу и как использовать фантастические автоматически сгенерированные хуки в наших компонентах. Позже я опубликую последующие статьи, объясняющие более продвинутые функции и концепции rtk-запросов, такие как кэширование, выборка конфигурации, преобразование данных и т. д. сильный>

Полная кодовая база всего, что мы рассмотрели, опубликована в этом репозитории и развернута на этой странице.

Если у вас есть какие-либо рекомендации или предложения, не стесняйтесь спрашивать меня по моей личной почте или добавлять комментарии, и если вам понравилась эта статья, не забудьте похлопать!

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.