React

Подъём состояния

Два компонента должны показывать одни и те же данные, но каждый завёл своё состояние - и они разъехались. Поле температуры в Цельсиях показывает 100, а в Фаренгейтах - устаревшее значение. Список отфильтрован, а счётчик найденного отстал. Это классическая ловушка: дублирование состояния. Решение придумали ещё на заре React и назвали lifting state up - подъём состояния. Вместо двух копий значение поднимают в общего родителя, делают его единственным источником правды, и оба компонента всегда смотрят на одни данные.

  • Связанные поля формы: выбор страны фильтрует список городов - оба зависят от состояния, поднятого в родителя
  • Фильтр и список результатов в каталоге читают один query из общего родителя и всегда согласованы
  • Табы и панель содержимого: активная вкладка хранится в родителе, который решает, что показать
  • Когда поднятого состояния становится много, его выносят в Context или Zustand, но принцип единого источника правды остаётся тем же

Предварительные знания

  • useState: объявление состояния и иммутабельное обновление
  • События и обработчики: передача колбэка вниз через props
  • Компоненты и props: данные текут сверху вниз, props доступны только для чтения

Проблема дублирующегося состояния

Когда каждый компонент держит свою копию данных, которые на самом деле должны быть общими, рано или поздно копии расходятся. Изменили одну - вторая об этом не знает. Это нарушает базовый принцип: у каждой порции данных должен быть один источник правды. Симптом такой проблемы - попытка вручную синхронизировать состояние двух компонентов через эффекты или колбэки, копирующие значение туда-сюда.

Здесь два поля описывают одну температуру в разных шкалах, но каждое хранит своё значение. Ввод в Цельсии никак не отразится на Фаренгейтах. Чтобы они стали согласованными, температуру нужно сделать одним общим состоянием, а не двумя независимыми.

Синхронизация двух состояний через useEffect, копирующий одно значение в другое - почти всегда признак того, что состояние нужно было поднять, а не дублировать. Лишнее состояние и попытки его синхронизировать - типичный источник багов.

Почему хранение одних и тех же данных в двух компонентах ведёт к багам?

Подъём в общего родителя

Решение в три шага. Первый: убрать состояние из дочерних компонентов. Второй: поднять его в ближайший общий родитель. Третий: передать вниз два props - текущее значение и колбэк для его изменения. После этого дочерние компоненты становятся контролируемыми: они не хранят своё состояние, а читают значение из props и сообщают об изменениях через колбэк. Источник правды один - родитель.

TempInput больше не владеет данными. Он получает value сверху и при вводе вызывает onChange, отдавая новое значение наверх. Родитель обновляет своё состояние, и новое значение спускается обратно. Это и есть формула React: данные вниз через props, события вверх через колбэки.

Поднимать состояние выше необходимого не стоит: чем выше оно живёт, тем больше промежуточных компонентов вынуждены прокидывать props насквозь (prop drilling). Оптимальное место - именно ближайший общий родитель тех, кому состояние реально нужно.

Что родитель передаёт дочернему компоненту при подъёме состояния?

Синхронизация соседних компонентов

Главная выгода подъёма раскрывается, когда состояние читают сразу несколько детей. Родитель спускает одно и то же значение всем, кому оно нужно. Стоит одному ребёнку вызвать колбэк, родитель обновит состояние и перерисуется, а вместе с ним - все дети, получающие это значение через props. Соседние компоненты автоматически остаются согласованными, потому что смотрят в один источник.

SearchInput меняет query наверху, а ResultCount и список читают производные от того же query. Счётчик никогда не отстанет от списка, потому что оба пересчитываются из единого состояния при каждом ре-рендере родителя. Дублирования нет - есть один источник правды и согласованный интерфейс.

Если поднятое состояние приходится прокидывать через много промежуточных слоёв, это сигнал перейти к Context для устранения prop drilling, а для крупного клиентского состояния - к Zustand. Оба инструмента сохраняют принцип единого источника правды, просто делают доступ к нему удобнее.

Почему после подъёма состояния соседние компоненты остаются синхронными?

Связь с другими темами

Подъём состояния - кульминация модуля про интерактивность. Дальше он масштабируется:

  • Thinking in React — Подъём состояния - финальный, пятый шаг официального метода проектирования
  • События и обработчики — Событие наверх - это вызов колбэка, переданного родителем вниз
  • Что такое render — При подъёме состояния обновление родителя перерисовывает оба зависимых компонента

Итог

  • Если состояние нужно нескольким компонентам, его поднимают в ближайший общий родитель - это единственный источник правды
  • Родитель хранит состояние и передаёт вниз два вида props: само значение и колбэк для его изменения
  • Данные текут вниз через props, события идут вверх через вызов переданных колбэков
  • Дочерние компоненты становятся контролируемыми: своего состояния не держат, всё берут из props
  • Так соседние компоненты всегда синхронны: оба читают одно значение из одного места
  • Когда поднятого состояния много, его выносят в Context или внешний стор вроде Zustand

Связанные уроки

  • rc-06-usestate — Подъём состояния перемещает useState в родителя, поэтому сначала нужно владеть самим useState
  • rc-07-events-handlers — Событие вверх реализуется колбэком, переданным вниз через props - механизм из урока про события
  • rc-05-thinking-in-react — Пятый шаг метода 'Thinking in React' про обратный поток данных - это и есть подъём состояния
Подъём состояния

0

1

Войти