Рефакторинг приложения 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 может значительно сократить объем написанного кода, удалив шаблоны, необходимые для написания действий и редукторов, которые включают случаи для обработки состояний загрузки и ошибок. Кэширование ответов значительно сократит время рендеринга ваших приложений и позволит компонентам повторно использовать одни и те же запросы без отправки множества ненужных запросов на ваш сервер.