Совместное использование состояния между компонентами¶
Иногда требуется, чтобы состояние двух компонентов всегда изменялось вместе. Чтобы сделать это, удалите состояние из обоих компонентов, переместите его в их ближайшего общего родителя, а затем передайте его им через props. Это известно как поднятие состояния вверх, и это одна из самых распространенных вещей, которые вы будете делать при написании кода React.
Вы узнаете
- Как делиться состоянием между компонентами, поднимая его вверх
- Что такое управляемые и неуправляемые компоненты
Поднятие состояния на примере¶
В этом примере родительский компонент Accordion отображает две отдельные Panel:
AccordionPanelPanel
Каждый компонент Panel имеет булево состояние isActive, которое определяет, видно ли его содержимое.
Нажмите кнопку Show для обеих панелей:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
Обратите внимание, что нажатие на кнопку одной панели не влияет на другую - они независимы.
Изначально состояние isActive каждой Panel равно false, поэтому они обе отображаются свернутыми
Нажатие на кнопку любой Panel приведет только к обновлению состояния isActive этой панели.
Но теперь давайте предположим, что вы хотите изменить это так, чтобы только одна панель была развернута в любой момент времени. При таком дизайне, развертывание второй панели должно свернуть первую. Как это сделать?
Чтобы скоординировать эти две панели, вам нужно "поднять их состояние вверх" к родительскому компоненту в три шага:
- Удалите состояние из дочерних компонентов.
- Передать жестко закодированные данные от общего родителя.
- Добавить состояние в общий родительский компонент и передать его вниз вместе с обработчиками событий.
Это позволит компоненту Accordion координировать обе Panel и разворачивать только одну за раз.
Шаг 1: Удалите состояние из дочерних компонентов¶
Вы передадите контроль над isActive панели ее родительскому компоненту. Это означает, что родительский компонент будет передавать isActive в Panel в качестве пропса. Начните с удаления этой строки из компонента Panel:
1 | |
И вместо этого добавьте isActive в список пропсов Panel:
1 2 3 | |
Теперь родительский компонент Panel может контролировать isActive, передавая его как prop. И наоборот, компонент Panel теперь не имеет контроля над значением isActive - теперь это зависит от родительского компонента!
Шаг 2: Передача жестко закодированных данных от общего родителя¶
Чтобы поднять состояние вверх, вы должны найти ближайший общий родительский компонент обоих дочерних компонентов, которые вы хотите скоординировать:
Accordion(ближайший общий родитель).PanelPanel
В данном примере это компонент Accordion. Поскольку он находится над обеими панелями и может управлять их пропсами, он станет "источником истины" для того, какая панель в данный момент активна. Заставьте компонент Accordion передавать жестко закодированное значение isActive (например, true) обеим панелям:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | |
Попробуйте отредактировать жестко закодированные значения isActive в компоненте Accordion и посмотрите результат на экране.
Шаг 3: Добавьте состояние к общему родителю¶
Поднятие состояния вверх часто меняет природу того, что вы храните в качестве состояния.
В данном случае только одна панель должна быть активна одновременно. Это означает, что общий родительский компонент Accordion должен отслеживать, какая панель является активной. Вместо значения boolean, он может использовать число в качестве индекса активной Panel для переменной state:
1 | |
Когда activeIndex равен 0, активна первая панель, а когда 1 - вторая.
Нажатие на кнопку "Показать" в любой Panel должно изменить активный индекс в Accordion. Панель Panel не может установить состояние activeIndex напрямую, потому что оно определено внутри Accordion. Компонент Accordion должен явно разрешить компоненту Panel изменить свое состояние путем передачи обработчика события в качестве пропса:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
Теперь <button> внутри Panel будет использовать пропс onShow в качестве обработчика события щелчка:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | |
Это завершает подъем состояния вверх! Перемещение состояния в общий родительский компонент позволило скоординировать две панели. Использование активного индекса вместо двух флагов "показано" обеспечило, что только одна панель активна в данный момент времени. А передача обработчика события дочернему компоненту позволила ему изменять состояние родительского компонента.
Изначально activeIndex Accordion равен 0, поэтому первая Panel получает isActive = true.
Когда состояние Accordion activeIndex меняется на 1, вторая Panel получает isActive = true вместо этого
Управляемые и неуправляемые компоненты
Обычно принято называть компонент с некоторым локальным состоянием "неуправляемым". Например, оригинальный компонент Panel с переменной состояния isActive является неуправляемым, потому что его родитель не может повлиять на то, активна панель или нет.
Напротив, можно сказать, что компонент является "управляемым", когда важная информация в нем определяется пропсами, а не его собственным локальным состоянием. Это позволяет родительскому компоненту полностью определять его поведение. Последний компонент Panel с пропсом isActive управляется компонентом Accordion.
Неуправляемые компоненты проще использовать в родительских компонентах, поскольку они требуют меньше настроек. Но они менее гибкие, когда вы хотите скоординировать их вместе. Управляемые компоненты максимально гибкие, но они требуют от родительских компонентов полной конфигурации с помощью пропсов.
На практике "управляемый" и "неуправляемый" не являются строгими техническими терминами - каждый компонент обычно имеет некоторую смесь локального состояния и пропсов. Тем не менее, это полезный способ говорить о том, как проектируются компоненты и какие возможности они предоставляют.
При написании компонента подумайте, какая информация в нем должна быть управляемой (через пропсы), а какая - неуправляемой (через состояние). Но вы всегда можете передумать и рефакторить позже.
Единый источник истины для каждого состояния¶
В приложении React многие компоненты будут иметь свое собственное состояние. Некоторые состояния могут "жить" рядом с листовыми компонентами (компоненты в нижней части приложения).
Этот принцип также известен как наличие "единого источника истины". Он не означает, что все состояние живет в одном месте, но что для каждой части состояния существует конкретный компонент, который хранит эту часть информации. Вместо того, чтобы дублировать общее состояние между компонентами, поднимите его к их общему общему родителю, и передайте его вниз дочерним компонентам, которым оно необходимо.
Ваше приложение будет меняться по мере работы над ним. Часто бывает, что вы перемещаете состояние вниз или назад вверх, в то время как вы все еще выясняете, где "живет" каждая часть состояния. Это все часть процесса!
Чтобы увидеть, как это выглядит на практике с несколькими другими компонентами, прочитайте Мыслим как React.
Итого
- Когда вы хотите скоординировать два компонента, переместите их состояние в их общего родителя.
- Затем передайте информацию вниз через props от их общего родителя.
- Наконец, передайте обработчики событий, чтобы дочерние компоненты могли изменять состояние родительского.
- Полезно рассматривать компоненты как "управляемые" (управляемые пропсами) или "неуправляемые" (управляемые состоянием).
Задачи¶
1. Синхронизированные входы¶
Эти два входа являются независимыми. Сделайте их синхронизированными: редактирование одного входа должно обновить другой вход с тем же текстом, и наоборот.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | |
Показать подсказку
Вам нужно поднять их состояние в родительский компонент.
Показать решение
Переместите переменную состояния text в родительский компонент вместе с обработчиком handleChange. Затем передайте их как пропсы обоим компонентам Input. Это обеспечит их синхронизацию.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
2. Фильтрация списка¶
В этом примере SearchBar имеет собственное состояние query, которое управляет вводом текста. Его родительский компонент FilterableList отображает список элементов, но он не учитывает поисковый запрос.
Используйте функцию filterItems(foods, query) для фильтрации списка в соответствии с поисковым запросом. Чтобы проверить ваши изменения, проверьте, что ввод "s" в поле ввода отфильтровывает список до "Суши", "Шашлык" и "Дим сам".
Обратите внимание, что filterItems уже реализован и импортирован, поэтому вам не нужно писать его самостоятельно!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
Показать подсказку
Вы захотите удалить состояние query и обработчик handleChange из SearchBar, и переместить их в FilterableList. Затем передайте их в SearchBar как пропсы query и onChange.
Показать решение
Поднимите состояние query в компонент FilterableList. Вызовите filterItems(foods, query) для получения отфильтрованного списка и передайте его вниз в List. Теперь изменение ввода запроса отражается в списке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
Источник — https://react.dev/learn/sharing-state-between-components



