State Management
Зачем нужен state management
Корзина интернет-магазина показывает 3 товара. Иконка в шапке показывает 2. Оба числа берутся из одного списка, но список скопирован в два разных места, и одно обновили, а другое забыли. Пользователь видит противоречие и теряет доверие к чекауту. Это не редкая экзотика, а самый частый класс багов в интерфейсах: одни и те же данные живут в нескольких местах и расходятся. State management это набор приёмов и инструментов, которые отвечают на вопрос где и как хранить данные, чтобы такого расхождения не случалось.
- Корзина и иконка количества товаров в шапке, которые должны всегда показывать одно и то же число
- Аватар и имя пользователя в десятке мест интерфейса: меню, профиль, комментарии, шапка
- Открытая модалка, выбранная вкладка, развёрнутый сайдбар - состояние UI, общее для несвязанных компонентов
- Фильтры каталога в URL, чтобы ссылку можно было отправить коллеге и увидеть тот же результат
- Онбординг-визард на пять шагов, где данные шага 1 нужны на шаге 4
Предварительные знания
- Понимание что такое компонентный интерфейс (дерево компонентов, как в React, Vue, Angular)
- Идея локального состояния компонента (например useState) на уровне знакомства
- Базовое представление о том, что UI это функция от данных: меняются данные - перерисовывается экран
Как из боли двусторонней привязки родились Flux и Redux
2014 год, Facebook. Команда столкнулась с багом счётчика непрочитанных сообщений: иконка показывала уведомления, которых уже не было, потому что данные обновлялись из нескольких мест и расходились. Двусторонняя привязка данных (модель меняет вид, вид меняет модель) превращала поток данных в запутанный граф, где причину рассинхрона было не отследить. На конференции F8 Джинг Чен представила Flux - архитектуру с однонаправленным потоком: action идёт в dispatcher, тот в store, store обновляет view, и больше никак. В 2015 году Дэн Абрамов, готовя доклад на React Europe, написал Redux - компактную реализацию идей Flux с одним store и чистыми редьюсерами. Redux дал предсказуемость и time-travel отладку, но позже выяснилось, что для многих задач он избыточен, и это породило целое поколение более лёгких инструментов.
Что такое состояние приложения
Состояние приложения это все данные, которые меняются во время работы и влияют на то, что отображается. Введённый в поле текст, список товаров в корзине, признак загрузки, открыта ли модалка, выбранная тема - всё это состояние. Современный интерфейс строится по принципу UI это функция от состояния: при изменении данных фреймворк перерисовывает соответствующие части экрана. Поэтому вопрос где живёт состояние определяет, насколько предсказуемо ведёт себя приложение.
- Локальное состояние компонента: открыт ли выпадающий список, текст в одном поле
- Общее состояние нескольких компонентов: выбранная тема, корзина, текущий пользователь
- Серверное состояние: список заказов, профиль - данные, которые на самом деле живут в базе на сервере
- Состояние навигации: какая страница открыта, какие фильтры в адресной строке
Ключевая мысль: не всё, что хранится в памяти, одинаково. Текст в одном поле и список заказов с сервера это разные сущности с разным жизненным циклом. Смешивать их в одном хранилище - источник большинства проблем, которые курс разбирает дальше.
Что из перечисленного точнее всего описывает состояние приложения?
Синхронизация, prop-drilling и рассинхрон
Хранить одно значение в одном компоненте просто. Боль начинается, когда одни и те же данные нужны в разных, далёких друг от друга частях дерева. Возникают три типичные проблемы: проброс данных через промежуточные компоненты (prop-drilling), синхронизация копий и рассинхрон интерфейса, когда копии разошлись.
Каждый промежуточный компонент вынужден знать про user, хотя сам его не использует. Добавление нового поля заставляет править всю цепочку, а промежуточные компоненты становятся связаны с данными, к которым отношения не имеют. Это нарушение принципа низкой связанности.
- Копирование данных (источник рассинхрона) — Список товаров скопирован в стейт корзины и отдельно в стейт иконки в шапке. Чтобы числа совпадали, оба надо обновлять синхронно. Один забытый апдейт - и UI противоречит сам себе.
- Один источник, много читателей — Список лежит в одном месте, а корзина и иконка читают из него. Изменение видно сразу везде, потому что копий нет и синхронизировать нечего.
Рассинхрон почти всегда возникает из дублирования. Как только одно и то же значение хранится в двух местах и обновляется независимо, вопрос не в том, разойдутся ли они, а когда. Поэтому борьба идёт не с симптомом (разными числами), а с причиной - наличием копий.
Какова корневая причина рассинхрона интерфейса, когда корзина показывает 3 товара, а иконка в шапке - 2?
Где настоящая боль и зачем инструменты
Настоящая боль не в том, чтобы сохранить одно число. Она в координации: много частей интерфейса зависят от одних данных, данные меняются из разных мест, и всё это должно оставаться согласованным. Инструменты state management существуют, чтобы дать общим данным одно каноническое место и чёткие правила изменения, вместо россыпи копий и ручной синхронизации.
| Проблема | Без инструмента | С инструментом state management |
|---|---|---|
| Общие данные | Копии в разных компонентах, ручная синхронизация | Одно хранилище, все читают из него |
| Проброс данных | Prop-drilling через всю цепочку | Компонент берёт нужное напрямую из стора |
| Изменение из разных мест | Непредсказуемый граф обновлений | Явные правила (например однонаправленный поток) |
| Отладка | Непонятно, кто и когда изменил данные | Прослеживаемая история изменений |
Именно из этой боли выросли Flux и Redux: они навели порядок в потоке данных, сделав его однонаправленным и прослеживаемым. Со временем стало ясно, что для разных видов состояния нужны разные инструменты, и единый тяжёлый стор не всегда оправдан. Но исходная задача неизменна: согласованность общих данных при изменениях из многих мест.
Важно: инструмент не обязателен для каждого приложения. Маленький виджет с локальным состоянием прекрасно живёт без библиотек. Инструменты включаются, когда общих данных и мест их изменения становится много, и ручная синхронизация начинает ломаться.
В чём состоит настоящая задача, которую решают инструменты state management?
Связь с другими темами
Этот урок объясняет саму проблему. Дальше курс раскладывает её на части:
- Таксономия состояния — Следующий шаг: понять, что состояние бывает разным (локальное, серверное, URL) и каждому нужен свой инструмент
- Единый источник истины — Прямой ответ на боль из этого урока: одно каноническое место для каждого куска данных
- Zustand — Современный лёгкий стор, выросший как ответ на избыточность Redux в типовых задачах
Итог
- Состояние приложения это данные, которые меняются во времени и определяют, что видит пользователь на экране
- Главная боль не в хранении одного значения, а в синхронизации одних и тех же данных между многими частями интерфейса
- Prop-drilling (проброс данных через цепочку промежуточных компонентов) делает код хрупким и связывает несвязанные компоненты
- Рассинхрон UI возникает, когда одни данные скопированы в несколько мест и обновляются независимо
- Flux (Facebook, 2014) предложил однонаправленный поток данных как лекарство от запутанной двусторонней привязки
- Redux (Дэн Абрамов, 2015) сделал идеи Flux компактными и предсказуемыми, но для многих задач оказался избыточен
Связанные уроки
- sm-02-state-taxonomy — Поняв зачем нужен state management, следующий шаг - разобрать виды состояния и подобрать инструмент под каждый
- rc-38-zustand-state — Zustand это один из современных ответов на проблемы, которые в этом уроке разбираются на уровне идеи