Урок 18: Redux DevTools Extension (отладка приложения)

Мы подходим к одному из основных моментов в изучении Redux. Если до этого мы вручную собирали наш магазин состояния, писали редьюсеры и настраивали слайсы, то сегодня мы наденем шляпу детектива. Мы вооружимся инструментом, который превратит поиск и устранение ошибок из рутины в настоящее удовольствие. Это инструмент, который покажет вам всё, что происходит внутри вашего приложения, вплоть до каждого маленького изменения состояния. Знакомьтесь с Redux DevTools Extension.

Этот урок станет переломным моментом в вашем понимании того, как можно эффективно работать со сложным состоянием. Мы не просто будем смотреть на логи в консоли, мы будем буквально «путешествовать во времени» по состоянию нашего приложения.

Как установить Redux DevTools?

Redux DevTools это расширение для браузеров (Chrome, Firefox, Edge), которое подключается к вашему Redux-стору и предоставляет мощный интерфейс для его отладки. Оно позволяет вам в реальном времени видеть все отправки actions, следить за тем, как они изменяют состояние и «отматывать» эти изменения назад и вперед.

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

Установка происходит в два простых шага:

  1. Установите расширение из магазина вашего браузера. Просто зайдите в Chrome Web Store, Firefox Add-ons или Microsoft Edge Add-ons и найдите «Redux DevTools». Установите его, как любое другое расширение.

  2. Настройте ваш Store. И вот здесь проявляется вся суть Redux Toolkit (RTK). Помните функцию configureStore? Она уже по умолчанию настраивает интеграцию с DevTools! Вам не нужно делать абсолютно ничего. Если расширение установлено, а ваш стор создан через configureStore, DevTools автоматически его «увидит».

Давайте взглянем на наш стандартный код создания стора:

javascript
// store.js
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    // ... другие редьюсеры
  },
})

Этого кода достаточно. Когда вы откроете инструменты разработчика в браузере (F12), у вас появится новая вкладка «Redux». Открыв ее, вы попадете в командный центр управления состоянием вашего приложения.

Практическая задача 1: Убедитесь, что расширение установлено и работает. Создайте простое React-приложение с одним слайсом (например счетчиком), как мы делали в предыдущих уроках. Запустите приложение, откройте DevTools (вкладку Redux) и понажимайте на кнопки + и -. Вы должны видеть, как в интерфейсе DevTools появляются записи о отправке actions.

Обзор панели управления

Когда вы впервые откроете вкладку Redux, вы можете немного растеряться от количества кнопок и информации. Давайте разберем самые важные из них.

Слева вы видите список всех действий (actions), которые были отправлены (dispatched) в вашем приложении с момента его загрузки. Каждое действие показано со своим типом (например counter/increment) и payload (полезной нагрузкой), если она была.

Справа находится инспектор состояния (State). По умолчанию открыта вкладка «State», которая показывает текущее, «финальное» состояние всего вашего приложения в виде дерева. Вы можете разворачивать и сворачивать ветки, чтобы посмотреть, что хранится внутри countertodos или любого другого слайса.

Но это лишь верхушка айсберга. Обратите внимание на другие вкладки:

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

  • Action. Показывает полную информацию о выбранном действии. Его тип, payload и метаданные.

  • Trace. Может показывать стек вызовов для асинхронных действий, помогая понять, откуда именно пришел вызов.

Самая нужная панель управления находится внизу. Это ваша «панель видеомонтажа» для состояния приложения.

javascript
// Пример действия, которое вы увидите в списке
{
  type: "counter/increment",
  payload: undefined,
  // DevTools также добавит свои метаданные, например, timestamp
}

Практическая задача 2: Модифицируйте ваш слайс счетчика, чтобы он принимал payload. Например, добавьте экшен incrementByAmount, который увеличивает счетчик на переданное число. Диспатчте его с разными значениями (dispatch(incrementByAmount(5))) и наблюдайте в DevTools, как меняется payload в списке действий и состояние на вкладке State.

Путешествие по времени для состояний (Time-Travel Debugging)

А теперь приготовьтесь к самому крутому, к Time-Travel Debugging. Это не просто модное словосочетание, это функциональность, которая кардинально меняет подход к отладке.

Посмотрите на нижнюю панель DevTools. Вы увидите кнопки, очень похожие на элементы управления видеоплеером:

  • «Прыгнуть» (Jump). Кнопки со стрелками. Позволяют пошагово перемещаться по истории действий.

  • «Воспроизвести» (Play). Автоматически «проигрывает» всю историю изменений состояния.

  • «Перемотать» (Skip). Позволяет пропустить определенное действие, эффективно удаляя его из истории и пересчитывая состояние заново.

Как это работает на практике? Допустим, у вас есть цепочка действий: increment -> decrement -> incrementByAmount(10). Ваш счетчик прошел значения 1, 0, 10.

  1. Вы можете нажать на действие decrement в списке слева.

  2. Состояние справа мгновенно «отмотается» к тому моменту, когда счетчик был равен 0.

  3. При этом в вашем реальном интерфейсе (в браузере) состояние также изменится! Вы будете видеть значение 0.

  4. Теперь вы можете «запустить» историю с этого момента заново, но с другим действием. Например, вы можете нажать кнопку incrementByAmount(20) в вашем приложении и история продолжится уже с новым действием.

Это дает вам возможность воспроизводить баги снова и снова, меняя условия. Вы можете найти момент, когда состояние стало неверным и проанализировать, какое действие и с какими данными привело к ошибке.

Практическая задача 3: В вашем приложении со счетчиком совершите 5 разных действий (увеличьте, уменьшите, добавьте кастомное число). Затем используйте кнопки «Jump» на нижней панели, чтобы перемещаться по ним. Обратите внимание, как меняется число в вашем интерфейсе на экране. Вы буквально управляете временем для состояния вашего приложения.

Инспектирование состояния и поиск узких мест

DevTools это не только отладка логики, но и анализ производительности. Давайте поговорим о вкладке «Inspector».

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

Как проводить нагрузочное тестирование?

Допустим, вы хотите понять, насколько быстро ваше приложение обрабатывает большое количество синхронных действий (например добавление сотни задач в список).

  1. В коде вашего компонента создайте кнопку, которая диспатчит 1000 экшенов increment в цикле.

    javascript
    // Где-то в компоненте
    const dispatch = useDispatch();
    
    const handleMassUpdate = () => {
      for (let i = 0; i < 1000; i++) {
        dispatch(increment());
      }
    };
    
    return <button onClick={handleMassUpdate}>Добавить 1000 раз!</button>;
  2. Нажмите на эту кнопку.

  3. В DevTools на вкладке «Inspector» вы увидите, как быстро (или медленно) обрабатывается эта пачка действий. Если есть серьезные «тормоза», это повод задуматься об оптимизации селекторов с помощью createSelector (о чем мы говорили в Уроке 8) или о реструктуризации состояния.

Этот метод помогает находить «узкие места» в вашей Redux-логике до того, как пользователи столкнутся с проблемами.

Находим и исправляем баг с помощью только Redux DevTools

Давайте смоделируем реальную ситуацию. У нас есть простой слайс для управления списком задач, но в нем есть ошибка.

Шаг 1: Создаем «багнутый» слайс

javascript
// features/todos/todosSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  items: [],
  filter: 'all',
};

const todosSlice = createSlice({
  name: 'todos',
  initialState,
  reducers: {
    addTodo: (state, action) => {
      // ОШИБКА: Мы мутируем состояние напрямую, не используя Immer корректно?
      // На самом деле, RTK использует Immer, так что это легально.
      // Но давайте представим, что здесь другая ошибка.
      state.items.push({ text: action.payload, completed: false });
    },
    toggleTodo: (state, action) => {
      // БАГ НАХОДИТСЯ ЗДЕСЬ!
      const index = action.payload; // Ожидаем индекс
      // Но что, если мы передадим не индекс, а id объекта?
      // Или если индекс будет неверным?
      state.items[index].completed = !state.items[index].completed;
    },
    setFilter: (state, action) => {
      state.filter = action.payload;
    },
  },
});

export const { addTodo, toggleTodo, setFilter } = todosSlice.actions;
export default todosSlice.reducer;

Шаг 2: Создаем простой компонент

javascript
// features/todos/TodoList.js
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addTodo, toggleTodo } from './todosSlice';

export const TodoList = () => {
  const dispatch = useDispatch();
  const todos = useSelector((state) => state.todos.items);
  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text) {
      dispatch(addTodo(text));
      setText('');
    }
  };

  // БАГ: Мы по ошибке передаем в toggleTodo текст задачи вместо ее индекса!
  const handleToggle = (todoText) => {
    // Это неправильно! toggleTodo ожидает индекс (number), а получает string.
    dispatch(toggleTodo(todoText));
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input value={text} onChange={(e) => setText(e.target.value)} />
        <button type="submit">Добавить</button>
      </form>
      <ul>
        {todos.map((todo, index) => (
          <li
            key={index}
            onClick={() => handleToggle(todo.text)} // Здесь ошибка!
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
};

Шаг 3: Воспроизводим баг и используем DevTools

  1. Запустите приложение.

  2. Добавьте 2-3 задачи. Например: «Изучить Redux», «Написать код», «Найти баг».

  3. Попробуйте нажать на любую задачу, чтобы переключить ее состояние. С большой вероятностью в консоли браузера вы увидите ошибку Uncaught TypeError: Cannot read properties of undefined и приложение может сломаться.

  4. Не перезагружайте страницу! Откройте Redux DevTools.

Шаг 4: Анализ в DevTools

  1. В списке действий слева вы увидите последовательность addTodo и последний экшен toggleTodo.

  2. Кликните на последний toggleTodo. На вкладке Action вы увидите, что в качестве payload был передан текст задачи, например, "Написать код". Это явно не то, что ожидал редьюсер!

  3. Теперь посмотрите на вкладку State в момент перед багнутым действием. Вы увидите массив items с вашими задачами. Понятно, что items["Написать код"] это undefined, отсюда и ошибка.

  4. Используйте Time-Travel. «Отмотайте» время назад, к моменту до вызова toggleTodo. Ваш интерфейс восстановится.

Шаг 5: Исправление бага

Теперь, поняв причину, мы можем ее исправить. Мы должны передавать в toggleTodo не текст, а индекс задачи.

javascript
// Исправленный компонент TodoList.js
// ...
  const handleToggle = (index) => { // Принимаем индекс
    dispatch(toggleTodo(index)); // Передаем индекс
  };
// ...
      <ul>
        {todos.map((todo, index) => (
          <li
            key={index}
            onClick={() => handleToggle(index)} // Передаем индекс, а не текст
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
          >
            {todo.text}
          </li>
        ))}
      </ul>
// ...

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

javascript
// Улучшенный редьюсер в todosSlice.js
    toggleTodo: (state, action) => {
      const index = action.payload;
      // Защитная проверка
      if (index >= 0 && index < state.items.length) {
        state.items[index].completed = !state.items[index].completed;
      }
    },

Вот так, используя только Redux DevTools, мы не только нашли и поняли баг, но и смогли безопасно «откатиться» к рабочему состоянию приложения, чтобы спокойно исправить код.

Итоги 18-го урока

Redux DevTools окно в душу вашего приложения. Умение пользоваться им, это навык, который отличает новичка от опытного разработчика. Вы перестаете гадать, «почему тут значение 5?» и начинаете точно знать всю цепочку событий, которая к этому привела.

Вы научились:

  • Устанавливать и настраивать расширение.

  • Мониторить действия и состояние.

  • Использовать Time-Travel Debugging.

  • Проверять производительность.

  • Проводить полный цикл отладки реального бага.

Чем сложнее состояние, тем больше пользы вы извлечете из DevTools.

Чтобы не пропустить следующие материалы и закрепить знания на большом проекте, переходите к полному курсу на моем сайте. Полный курс с уроками по Redux и Redux Toolkit для начинающих ждет вас по ссылке
https://max-gabov.ru/redux-dlya-nachinaushih

Поделиться статьей:
Поддержать автора блога

Поддержка автора осуществляется с помощью специальной формы ниже, предоставленной сервисом «ЮMoney». Все платёжные операции выполняются на защищённой странице сервиса, что обеспечивает их корректность и полную безопасность.

Персональные рекомендации
Оставить комментарий