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

Это проблематично по ряду причин, включая ненужный повторный рендеринг, трудности с изменением компонентов и усложнение чтения кода. Лично я предпочитаю полностью автономные компоненты, извлекающие и отображающие свои данные без зависимости от своих родительских компонентов. Это может привести к некоторым недостаткам, таким как ненужные вызовы API (о которых мы поговорим позже), но это упрощает понимание ваших компонентов, их рефакторинг, тестирование и повторное использование, если это необходимо.

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

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

Ниже приведен упрощенный пример того, как это можно сделать. Примечание. Редьюсеры и действия опущены для краткости.

// Before refactoring
const ParentComponent = ({ listProp, stringProp }) => {
  return (
    <div>
      <ChildComponentOne listProp={listProp} />
      <ChildComponentTwo stringProp={stringProp} />
    </div>
  );
};
// After refactoring ParentComponent
const ParentComponent = () => {
  return (
    <div>
      <ChildComponentOne />
      <ChildComponentTwo />
    </div>
  );
};

// After refactoring ChildComponentOne
ipmort { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchListAction } from 'MyStore/list

const ChildComponentOne = () => {
  const dispatch = useDispatch();
  const listProp = useSelector(state => state.someSlice.listProp);

  useEffect(() => {
    dispatch(fetchListAction)
  }, [])

  return (
    <ul>
      {listProp.map(item => <li key={item}>{item}</li>)}
    </ul>
  );
};

// After refactoring ChildComponentTwo
import { useSelector } from 'react-redux';
import { useSelector, useDispatch } from 'react-redux';
import { fetchStringAction } from 'MyStore/list

const ChildComponentOne = () => {
  const dispatch = useDispatch();
  const stringProp = useSelector(state => state.someSlice.stringProp);
  
  useEffect(() => {
    dispatch(fetchStringAction)
  }, [])

  return (
    <div>
      {stringProp}
    </div>
  );
};

В этом примере дочернему компоненту больше не нужно получать listProp и stringProp от своего родителя, вместо этого он извлекает эти реквизиты непосредственно из хранилища Redux с помощью метода useSelector. Дочерние компоненты также непосредственно отвечают за выборку данных.

Но что, если у вас есть несколько компонентов, которым необходимо отображать фрагменты данных, возвращаемых одним и тем же запросом API? Вы можете расширить свой вызов useEffect(), чтобы проверить статус вашего запроса API. В большой кодовой базе вы, вероятно, будете часто сталкиваться с этим и иметь много повторяющегося кода. Может, стоит сделать многоразовый крючок? Есть лучшее решение: Redux Toolkit Query (RTKQ).

Redux Toolkit Query — это мощная библиотека, которая упрощает процесс извлечения данных из API и управления состояниями загрузки и ошибок в магазине Redux. Хотя RTKQ может быть труден для понимания на начальном этапе — большая часть функций запутана — его использование может и, скорее всего, приведет к меньшему (и, следовательно, более простому в обслуживании) коду. Но самым большим преимуществом является то, что он может кэшировать ваши запросы, снижая нагрузку на ваш сервер API и ускоряя рендеринг ваших компонентов. Он предоставляет простой и декларативный API, который может значительно сократить объем шаблонного кода, необходимого для управления асинхронной выборкой данных в приложении Redux.

Чтобы использовать Redux Toolkit Query, сначала установите его как зависимость:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';

const apiSlice = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.example.com' }),
  endpoints: builder => ({
    getPosts: builder.query({
      query: () => '/posts',
    }),
  }),
});

export const { useGetPostsQuery } = apiSlice;

В этом примере срез API определяется одной конечной точкой, которая извлекает список сообщений из API. Затем хук useGetPostsQuery можно использовать в компоненте React для извлечения данных и автоматического управления состояниями загрузки и ошибок.

Вот пример того, как это можно использовать в компоненте:

import { useGetPostsQuery } from './apiSlice';

const MyComponent = () => {
  const { data, isLoading, isError } = useGetPostsQuery();

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

  if (isError) {
    return <div>Error loading posts</div>;
  }

  return (
    <div>
      {data.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
};

Как вы можете видеть выше, использование RTKQ может значительно сократить объем написанного кода, удалив шаблоны, необходимые для написания действий и редукторов, которые включают случаи для обработки состояний загрузки и ошибок. Кэширование ответов значительно сократит время рендеринга ваших приложений и позволит компонентам повторно использовать одни и те же запросы без отправки множества ненужных запросов на ваш сервер.