JavaRush /Курсы /Модуль 3: React /Сложные кейсы при использовании Redux и Redux Toolkit

Сложные кейсы при использовании Redux и Redux Toolkit

Модуль 3: React
6 уровень , 9 лекция
Открыта

Решение сложных задач при использовании Redux

1. Как масштабировать Redux-приложение?

Когда ваш проект становится большим, проблема масштабируемости становится особенно заметной. И вот здесь начинается вечная борьба между "удобством" и "поддерживаемостью". Если ваш store начинает напоминать лего-замок, слепленный без инструкции, пора пересмотреть структуру.

Решение: Модульный подход

Организуйте код по модулям или фичам. Каждая функциональность (например, "пользователь", "задачи", "уведомления") может иметь:

  • Свой Slice.
  • Отдельные действия и редьюсеры.
  • В случае необходимости: middleware и thunks.

Пример структуры:

src/
  features/
    user/
      userSlice.ts
      userActions.ts
      userSelectors.ts
    tasks/
      tasksSlice.ts
      tasksActions.ts
      tasksSelectors.ts

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

Частая ошибка:

Централизованное глобальное состояние. Например, представьте, что вы решили положить всё состояние в один Slice. Это звучит как "удобно" на первых порах, но позже может стать катастрофой. Разделяйте задачи!

2. Сложные зависимости между срезами

Представьте ситуацию: у вас есть два Slice — userSlice и tasksSlice. Задачи tasks зависят от текущего пользователя user. Как синхронизировать эти зависимости?

Решение: Middleware или Thunk

Используйте createAsyncThunk или кастомное middleware для управления зависимостями.

Пример: удаление пользователя должно также очистить связанные задачи.

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
import { RootState } from './store';

export const deleteUserAndTasks = createAsyncThunk<void, string, { state: RootState }>(
  'user/deleteUserAndTasks',
  async (userId, { dispatch, getState }) => {
    // Удаляем пользователя
    dispatch(deleteUser(userId));

    // Очищаем задачи связанные с пользователем
    const state = getState();
    const tasks = state.tasks.filter(task => task.userId === userId);
    tasks.forEach(task => dispatch(deleteTask(task.id)));
  }
);

Распространенные проблемы, связанные с производительностью

1. Лишние рендеры компонентов

Представьте массив задач, хранящийся в state, и компонент, отвечающий за их рендеринг. Даже если изменится только одна задача, внезапно рендерятся все задачи.

Решение: мемоизация селекторов и компонентов

Используйте reselect для создания мемоизированных селекторов. Это позволит избежать лишних вычислений.

Пример:

import { createSelector } from '@reduxjs/toolkit';

const selectTasks = (state: RootState) => state.tasks;
const selectCompletedTasks = createSelector(
  [selectTasks],
  (tasks) => tasks.filter(task => task.completed)
);

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

import React from 'react';
import { useSelector } from 'react-redux';
import { selectCompletedTasks } from './tasksSlice';

const CompletedTasks = React.memo(() => {
  const completedTasks = useSelector(selectCompletedTasks);
  return (
    <ul>
      {completedTasks.map(task => <li key={task.id}>{task.title}</li>)}
    </ul>
  );
});

2. Перегруженный глобальный store

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

Решение: состояние локальное против глобального

Не храните всё и вся в Redux. Например:

  • Локальные фильтры или состояния модальных окон лучше держать в useState внутри компонентов.
  • Глобальное состояние оставьте для вещей, используемых многими компонентами.

Архитектура Redux-приложений

Middleware: Спасение или зло?

Middleware, как и настоящие медвежатники, может решить массу задач: от логирования до обработки побочных эффектов. Но будьте осторожны, слишком много middleware может сделать ваше приложение хаотичным.

Пример полезного middleware: логирование

const loggerMiddleware = (storeAPI: MiddlewareAPI) => (next: Dispatch) => (action: AnyAction) => {
  console.log('Dispatching:', action);
  const result = next(action);
  console.log('Next state:', storeAPI.getState());
  return result;
};

Обработка ошибок

Если вы работали с асинхронными экшенами, то наверняка сталкивались с проблемой: "Что делать, если запрос упал?". Redux Toolkit может обрабатывать ошибки, но позаботьтесь о сообщениях для пользователя.

Пример обработки ошибок:

export const fetchData = createAsyncThunk(
  'data/fetch',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/data');
      if (!response.ok) {
        throw new Error('Ошибка загрузки данных');
      }
      return response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

Советы и рекомендации

1. Разделяйте состояния по фичам

Не стремитесь к "абсолютной чистоте", но старайтесь избегать глобальных сущностей-супергероев. Лучше сделать несколько независимых модулей, чем один монолит.

2. Пользуйтесь возможностями Redux Toolkit

RTK уже реализовал множество устоявшихся практик: иммутабельность, структуры для thunk-ов, безопасные методы изменения состояния через immer.

3. Логическое разделение экшенов и редьюсеров

Когда редьюсер обрабатывает больше 10-15 экшенов, стоит задуматься о рефакторинге — возможно, его пора разбить на несколько.

4. Как тестировать Redux?

Используйте Jest с моками для тестирования редьюсеров:

import { reducer, addTask } from './tasksSlice';

test('добавление задачи', () => {
  const initialState = { tasks: [] };
  const newState = reducer(initialState, addTask({ id: '1', title: 'Test Task' }));
  expect(newState.tasks).toHaveLength(1);
  expect(newState.tasks[0].title).toBe('Test Task');
});

Redux может помочь с кучей сложных задач, если использовать его разумно. Не бойтесь экспериментировать и пробовать разные подходы — и да пребудет с вами сила мемоизации!

1
Задача
Модуль 3: React, 6 уровень, 9 лекция
Недоступна
Устранение лишних рендеров компонентов
Устранение лишних рендеров компонентов
3
Опрос
Использование createAsyncThunk, 6 уровень, 9 лекция
Недоступен
Использование createAsyncThunk
Использование createAsyncThunk
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ