State Management
MobX: reactions
Черновик письма должен сам сохраняться в localStorage при каждом изменении текста. Фильтры в поиске должны слать запрос только когда меняется сама строка фильтра, а не любое поле стора. Модалка приветствия должна показаться один раз, когда профиль наконец загрузился, и больше не возвращаться. Три разных побочных эффекта на изменение состояния, и в MobX под каждый есть своя реакция: autorun, reaction и when.
- Автосохранение черновиков и настроек в localStorage при изменении состояния
- Запрос к серверу при смене фильтров, сортировки или строки поиска
- Логирование и аналитика на изменение конкретных полей домена
- Однократные эффекты по условию: показать онбординг, когда данные загрузились
- Синхронизация observable-стора с внешним миром: URL, заголовок вкладки, вебсокет
Предварительные знания
- observable, computed и action из предыдущего урока MobX
- Идея автоматического отслеживания зависимостей по прочитанным полям
- Понятие побочного эффекта и необходимости его очистки
autorun: эффект на любое прочитанное изменение
autorun принимает функцию-эффект и запускает её немедленно один раз. Во время запуска MobX отслеживает, какие observable и computed были прочитаны, и подписывает эффект на них. Дальше эффект перезапускается при каждом изменении любого из прочитанных значений. Список зависимостей выводится автоматически, как у computed, но autorun нужен не для значения, а для побочного действия.
Эффект читает draft.text, поэтому autorun подписался на это поле. При каждом setText текст пишется в localStorage. autorun возвращает функцию-disposer: вызов dispose() останавливает наблюдение, и эффект больше не запускается. Без вызова disposer реакция живёт, пока живёт приложение, и это частый источник утечек.
autorun перезапускается на изменение любого observable, прочитанного внутри. Если в эффекте по неосторожности прочитать лишнее поле, реакция начнёт срабатывать чаще, чем задумано. Внутри эффекта читают ровно те данные, от которых он должен зависеть, и ничего сверх того.
Как autorun определяет, на какие изменения реагировать?
reaction: разделение данных и эффекта
reaction принимает две функции. Первая, data-функция, возвращает значение, за которым нужно следить. Вторая, эффект, получает это значение и выполняется только когда оно изменилось. В отличие от autorun, эффект не запускается сразу и не зависит от всего прочитанного: отслеживается ровно то, что вернула data-функция.
Здесь эффект бежит только при изменении search.query. Смена search.page реакцию не трогает, потому что page не возвращается из data-функции. Если бы тот же эффект написали через autorun и внутри прочитали и query, и page, запрос летел бы и при смене страницы. reaction даёт точный контроль над тем, что именно служит триггером.
- autorun — Запускается сразу и реагирует на всё прочитанное внутри. Удобно для эффекта, который зависит от многих полей сразу
- reaction — Не запускается сразу, отслеживает только результат data-функции и даёт в эффект старое и новое значение. Точный триггер
Эффект reaction получает не только новое значение, но и предыдущее вторым аргументом. Это удобно, когда реакция зависит от направления изменения: например, слать запрос только если строка действительно стала длиннее, а не просто изменилась.
Чем reaction отличается от autorun?
when: однократный эффект по условию
when ждёт, пока заданное условие станет истинным. Как только predicate-функция вернёт true, when выполняет эффект один раз и автоматически останавливает наблюдение. Это реакция для одноразовых сценариев: дождаться загрузки данных, дождаться готовности соединения, показать что-то ровно один раз.
Здесь модалка покажется ровно один раз, когда profile.loaded станет true, и больше when наблюдать не будет: он сам отписался. Не нужно вручную вызывать disposer для штатного завершения, хотя его всё равно возвращают на случай, если эффект нужно отменить до выполнения условия. У when есть и промис-форма без второго аргумента, которую можно дождаться через await.
Сводка выбора: autorun когда эффект зависит от многих полей и должен бежать сразу и при каждом их изменении. reaction когда триггером служит одно конкретное значение и сразу запускать не нужно. when когда эффект одноразовый и привязан к достижению условия. Все три это про побочные эффекты, а чистые производные значения остаются за computed.
Нужно показать онбординг ровно один раз, как только профиль загрузится, и больше за этим не следить. Какая реакция подходит лучше всего?
Связь с другими темами
Этот урок про побочные эффекты на изменение состояния. Рядом стоят соседи:
- MobX: observable, computed, action — Реакции наблюдают именно за observable и computed, которые разбирались в предыдущем уроке
- MobX: архитектура — Реакции мостят доменные сторы с эффектами: персистентностью, логами, синхронизацией
Итог
- Реакции это побочные эффекты, которые MobX запускает при изменении наблюдаемого состояния
- autorun запускается сразу и затем при каждом изменении любого observable, прочитанного внутри
- reaction разделяет отслеживаемые данные и эффект: эффект бежит только при изменении значения из data-функции
- when ждёт, пока условие станет истинным, выполняет эффект один раз и автоматически отписывается
- Каждая реакция возвращает функцию-disposer, и её нужно вызвать, чтобы остановить наблюдение
- Реакции для побочных эффектов, computed для чистых производных значений: их не смешивают
Связанные уроки
- sm-22-mobx-observables — Реакции запускаются на изменение observable и computed, поэтому их механика нужна заранее
- sm-24-mobx-architecture — В архитектуре реакции связывают доменные сторы с эффектами вроде сохранения и логирования