State Management
Devtools и тестирование
Пользователь жалуется: корзина обнулилась после применения промокода. Воспроизвести вручную не выходит. Разработчик открывает Redux DevTools, видит ленту action, отматывает состояние на шаг назад - и обнаруживает, что обработчик промокода случайно вернул пустой объект вместо обновлённой корзины. Без истории и time-travel этот баг искали бы часами по логам. Предсказуемое ядро состояния - чистый редьюсер, сериализуемые action - даёт две вещи разом: инструменты отладки и лёгкое тестирование.
- Redux DevTools с лентой action и time-travel для поиска момента, где состояние пошло не так
- Devtools-middleware Zustand, показывающий изменения стора в той же панели Redux DevTools
- Визуализатор XState от Stately, рисующий машину состояний как наглядную диаграмму переходов
- Юнит-тесты чистых редьюсеров: на вход состояние и action, на выходе сверяется новое состояние
- Команды, тестирующие стор и машину состояний без рендера UI, потому что логика отделена от вида
Предварительные знания
- Устройство Redux: action, чистый reducer и единый store
- Чистая функция: один и тот же вход даёт один и тот же выход без побочных эффектов
- Идея сериализуемого action: его можно записать, передать и воспроизвести
Redux DevTools и time-travel
Redux DevTools это расширение браузера, которое показывает поток состояния. Каждый диспатченный action попадает в ленту с типом и payload. Рядом видно текущее состояние store и diff, который внёс последний action. Главная возможность - time-travel: состояние можно отмотать на любой прошлый шаг и посмотреть, как приложение выглядело в тот момент.
Работает это потому, что ядро Redux предсказуемо. Состояние меняется только через action, а reducer - чистая функция. Значит, последовательность action полностью определяет состояние: повторив ту же ленту, всегда получаем тот же результат. DevTools хранят историю action и пересчитывают состояние для любого выбранного шага, отсюда и time-travel.
- Лента action: тип и payload каждого изменения по порядку
- Diff состояния: что именно изменилось после последнего action
- Time-travel: переход к состоянию на любом прошлом шаге
- Импорт и экспорт сессии: ленту action можно сохранить и воспроизвести у себя
Возможность отмотать и воспроизвести опирается на сериализуемость action. Если в action кладут несериализуемое (функцию, промис, инстанс класса), DevTools не смогут его сохранить и воспроизвести. Поэтому action держат как простые сериализуемые объекты.
Благодаря какому свойству Redux возможен time-travel в DevTools?
Devtools Zustand и визуализатор XState
Инструменты отладки не привязаны к Redux. Zustand подключается к той же панели Redux DevTools через devtools-middleware: стор оборачивают, и его изменения появляются в ленте с именами действий. Это даёт ту же историю и наблюдение за состоянием без перехода на Redux.
Третий аргумент set это имя действия в ленте DevTools: 'increment' появится в истории как подписанный шаг, а не безымянное изменение. Так стор Zustand получает наблюдаемость уровня Redux при своей минимальной обвязке.
Для машин состояний есть отдельный класс инструментов. Визуализатор XState от Stately рисует машину как диаграмму: состояния это узлы, переходы это стрелки с именами событий. Машину видно целиком - какие состояния есть, по какому событию идёт переход, какие переходы невозможны. Это превращает логику переходов из кода в наглядную карту.
- Лента action (Redux, Zustand devtools) — Хронология изменений во времени: какой action когда сработал и как поменял состояние. Отвечает на вопрос что произошло и в каком порядке
- Диаграмма машины (визуализатор XState) — Структурная карта логики: какие состояния возможны и по каким событиям между ними переходят. Отвечает на вопрос какие переходы вообще допустимы
Лента action и диаграмма машины дополняют друг друга. Лента показывает конкретную историю одного прогона во времени, диаграмма показывает всю структуру допустимых состояний и переходов сразу. Первое помогает в отладке инцидента, второе - в проектировании и обзоре логики.
Что показывает визуализатор XState от Stately в отличие от ленты action в Redux DevTools?
Тестирование чистого ядра
У предсказуемого ядра есть второе следствие после отладки - лёгкое тестирование. Чистый редьюсер это функция без побочных эффектов: на вход состояние и action, на выход новое состояние. Такую функцию тестируют напрямую, без рендера компонентов, без DOM и без моков. Тест просто вызывает редьюсер и сверяет результат.
Второй expect проверяет неизменяемость: исходное состояние не должно мутировать. Чистый редьюсер обязан вернуть новый объект, а не править before на месте. Это тестируется тем же вызовом, и обе гарантии - правильный результат и отсутствие мутации - проверяются за один прогон.
Стор Zustand тестируется похоже: вызвать действие через getState, затем сверить getState. Машину состояний XState тестируют, прогоняя события и проверяя итоговое состояние и допустимость переходов. Во всех трёх случаях логика отделена от представления, поэтому проверяется без UI. Это прямое преимущество того, что состояние живёт в чистых функциях.
Отделение логики состояния от представления это то, что делает тесты быстрыми и устойчивыми. Редьюсер, стор и машина проверяются как обычные функции, без хрупких тестов, дёргающих DOM. Предсказуемое ядро окупается дважды: в отладке через DevTools и в тестах через чистоту.
Почему чистый редьюсер тестируется без рендера компонентов и DOM?
Связь с другими темами
Этот урок про отладку и тесты. В основе лежит устройство Redux:
- Ядро Redux — Чистый reducer и сериализуемые action это то, что делает возможными и DevTools, и простые юнит-тесты
Итог
- Redux DevTools показывают ленту action, текущее состояние и позволяют отматывать его назад через time-travel
- Devtools-middleware Zustand подключает стор к той же панели Redux DevTools, давая историю изменений
- Визуализатор XState от Stately рисует машину состояний как диаграмму состояний и переходов
- Чистый редьюсер тестируется напрямую: на вход состояние и action, на выходе сверяется ожидаемое состояние
- Стор и машину состояний тестируют без рендера UI, потому что логика состояния отделена от представления
- Предсказуемое ядро состояния даёт отладку и тестирование разом: это следствие чистоты, а не отдельная библиотека
Связанные уроки
- sm-11-redux-core — Redux DevTools и тестирование чистых редьюсеров опираются на устройство Redux: action, reducer и единый store