JavaRush /Курсы /Модуль 3: React /Сложные случаи и типичные ошибки при работе с Redux в Rea...

Сложные случаи и типичные ошибки при работе с Redux в React Native

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

Хотя Redux — мощный инструмент, он легко превращается в источник боли, если использовать его неправильно. Давайте разберём ошибки, которые чаще всего встречаются у разработчиков, и постараемся понять, как их избежать.

Ошибка 1: чрезмерное дублирование логики

Один из самых распространённых сценариев — дублирование кода в разных частях приложения. Например, вы пишете кучу одинаковых обработчиков состояний в нескольких слайсах или добавляете сходные функции в разных компонентах. Это не только увеличивает объём кода, но и затрудняет его поддержку.

Как избежать:

Используйте утилитарные функции и общие действия. Например, если вам нужно обнулить состояние нескольких слайсов при логауте, можно использовать extraReducers в createSlice и одно общее действие:

import { createSlice, PayloadAction } from '@reduxjs/toolkit';

// Общий экшен для сброса состояния
const resetState = 'app/resetState';

const userSlice = createSlice({
  name: 'user',
  initialState: { name: '', email: '' },
  reducers: {
    setUser(state, action: PayloadAction<{ name: string; email: string }>) {
      state.name = action.payload.name;
      state.email = action.payload.email;
    },
  },
  extraReducers: (builder) => {
    builder.addCase(resetState, () => {
      return { name: '', email: '' }; // Сброс состояния
    });
  },
});

export const { setUser } = userSlice.actions;

// Использование в компоненте:
dispatch({ type: resetState });

Ошибка 2: хранилище перегружено данными

Некоторые разработчики склонны складывать всё в Redux. Например, данные полей формы, которые можно было бы локально хранить в useState, или большой JSON-объект с API, который используется только на одной странице. Это приводит к избыточному росту store и снижению производительности приложения.

Как избежать:

Старайтесь использовать локальное состояние (useState, useReducer в компонентах) для данных, которые не нужны в глобальном масштабе. Redux — для действительно глобального состояния, например, данных пользователя, токенов, тем приложения.

Пример: локальное состояние полей формы

const [formData, setFormData] = React.useState({ name: '', email: '' });

return (
  <TextInput
    value={formData.name}
    onChangeText={(text) => setFormData({ ...formData, name: text })}
  />
);

Ошибка 3: неправильная работа с асинхронными действиями

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

Как избежать:

Убедитесь, что вы учитываете все состояния загрузки (pending, fulfilled, rejected) и корректно их обрабатываете. Вот пример безопасного выполнения асинхронного действия:

export const fetchUser = createAsyncThunk(
  'user/fetchUser',
  async (userId: string, { rejectWithValue }) => {
    try {
      const response = await fetch(`https://api.example.com/users/${userId}`);
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

const userSlice = createSlice({
  name: 'user',
  initialState: { data: null, status: 'idle', error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.payload as string;
      });
  },
});

Ошибка 4: использование больших вложенных объектов

Разрастание структуры хранилища и попытки управлять глубокими вложенными объектами могут привести к проблемам обновления данных. Например, если вам нужно обновить одно поле в глубоко вложенном объекте, это может быть сложно, если вы не используете Immutable.js или аналогичные библиотеки.

Как избежать:

Разделяйте данные на более простые структуры. Если необходимо сохранять связанные данные, используйте нормализацию. Вместо хранения сложного объекта "пользователь" храните его ID и получайте остальные данные из другого среза.

const initialState = {
  users: {
    byId: {
      '1': { id: '1', name: 'Alice' },
      '2': { id: '2', name: 'Bob' },
    },
    allIds: ['1', '2'],
  },
};

Ошибка 5: неоптимизированные рендеры компонентов

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

Как избежать:

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

import { createSelector } from 'reselect';

const selectUsers = (state: RootState) => state.users.byId;
const selectActiveUserId = (state: RootState) => state.activeUserId;

export const selectActiveUser = createSelector(
  [selectUsers, selectActiveUserId],
  (users, activeUserId) => users[activeUserId]
);

Ошибка 6: отсутствие проверки на null или undefined

В компонентах, которые используют данные из Redux, нередки ошибки, связанные с отсутствием проверки на наличие данных. Вы пытаетесь рендерить data.name, а data ещё не загружено.

Как избежать:

Позаботьтесь о ручной проверке в компонентах или настройте дефолтные значения в хранилище.

const userData = useSelector((state: RootState) => state.user.data);

if (!userData) {
  return <LoadingIndicator />;
}

return <Text>{userData.name}</Text>;

Разбор сложных случаев

Кейс 1: сложная фильтрация и сортировка данных

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

Решение:

Сохраняйте исходные данные в Redux, а отфильтрованные или отсортированные данные вычисляйте локально в компоненте.

const products = useSelector((state: RootState) => state.products.all);
const [filter, setFilter] = React.useState('all');

const filteredProducts = React.useMemo(
  () => products.filter((product) => product.category === filter),
  [products, filter]
);

Кейс 2: подключение Redux Persist для нескольких срезов

Если нужно сохранить только часть состояния, а остальное оставить временным, используйте whitelist или blacklist в Redux Persist.

const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth'], // сохраняем только auth
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
2
Задача
Модуль 3: React, 23 уровень, 9 лекция
Недоступна
Обработка ошибок в асинхронных действиях
Обработка ошибок в асинхронных действиях
3
Опрос
Типизация createAsyncThunk, 23 уровень, 9 лекция
Недоступен
Типизация createAsyncThunk
Типизация createAsyncThunk
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ