Совместное использование состояния между компонентами¶
Иногда требуется, чтобы состояние двух компонентов всегда изменялось вместе. Чтобы сделать это, удалите состояние из обоих компонентов, переместите его в их ближайшего общего родителя, а затем передайте его им через props
. Это известно как поднятие состояния вверх, и это одна из самых распространенных вещей, которые вы будете делать при написании кода React.
Вы узнаете
- Как делиться состоянием между компонентами, поднимая его вверх
- Что такое управляемые и неуправляемые компоненты
Поднятие состояния на примере¶
В этом примере родительский компонент Accordion
отображает две отдельные Panel
:
Accordion
Panel
Panel
Каждый компонент 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
(ближайший общий родитель).Panel
Panel
В данном примере это компонент 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