Spec-Zone.ru › React

Совместное использование состояния между компонентами

Иногда вам нужно, чтобы состояние двух компонентов всегда изменялось вместе. Для этого удалите состояние из обоих компонентов, перенесите его в их ближайшего общего родителя и затем передайте его им через свойства. Это называется подъёмом состояния вверх, и это одна из самых распространённых операций, которую вы будете выполнять при написании кода React.

Вы узнаете

  • Как совместно использовать состояние между компонентами, поднимая его вверх
  • Что такое управляемые и неуправляемые компоненты

Пример подъёма состояния вверх

В этом примере родительский Accordion компонент отображает два отдельных Panel компонента:

  • Accordion
    • Panel
    • Panel

Каждый Panel компонент имеет булево isActive состояние, которое определяет, виден ли его контент.

Нажмите кнопку «Показать» для обеих панелей:

import { useState } from 'react';

function Panel({ title, children }) {
  const [isActive, setIsActive] = useState(false);
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          Show
        </button>
      )}
    </section>
  );
}

export default function Accordion() {
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About">
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel title="Etymology">
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

Обратите внимание, что нажатие кнопки одной панели не влияет на другую панель — они независимы.

Diagram showing a tree of three components, one parent labeled Accordion and two children labeled Panel. Both Panel components contain isActive with value false.

Изначально состояние каждого Panel компонента isActive имеет значение false, поэтому они оба отображаются в свёрнутом виде

The same diagram as the previous, with the isActive of the first child Panel component highlighted indicating a click with the isActive value set to true. The second Panel component still contains value false.

Нажатие кнопки любого Panel компонента обновит только состояние его Panel компонента isActive

Но теперь предположим, что вы хотите изменить это таким образом, чтобы только одна панель была развернута в любой момент. С таким дизайном развертывание второй панели должно свернуть первую. Как это сделать?

Для координации этих двух панелей вам нужно «поднять их состояние вверх» до родительского компонента в три шага:

  1. Удалите состояние из дочерних компонентов.
  2. Передайте жёстко закодированные данные от общего родителя.
  3. Добавьте состояние общему родителю и передайте его вместе с обработчиками событий.

Это позволит компоненту Accordion координировать оба Panel компонента и разворачивать только один за раз.

Шаг 1: Удалите состояние из дочерних компонентов

Вы передадите контроль над Panel isActive своему родителю. Это означает, что родительский компонент будет передавать isActive компоненту Panel в качестве свойства вместо этого. Начните с удаления этой строки из компонента Panel:

const [isActive, setIsActive] = useState(false);

И вместо этого добавьте isActive в список свойств Panel:

function Panel({ title, children, isActive }) {

Теперь родительский компонент Panel может контролировать isActive , передавая его в качестве свойства. Наоборот, компонент Panel теперь не имеет контроля над значением isActive — теперь это зависит от родительского компонента!

Шаг 2: Передайте жёстко закодированные данные от общего родителя

Чтобы поднять состояние вверх, необходимо найти ближайший общий родительский компонент обоих дочерних компонентов, которые вы хотите скоординировать:

  • Accordion (ближайший общий родитель)
    • Panel
    • Panel

В этом примере это компонент Accordion. Поскольку он находится над обеими панелями и может контролировать их свойства, он станет «источником истины» для того, какая панель в данный момент активна. Сделайте, чтобы компонент Accordion передавал жёстко закодированное значение isActive (например, true) обеим панелям:

import { useState } from 'react';

export default function Accordion() {
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel title="About" isActive={true}>
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel title="Etymology" isActive={true}>
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({ title, children, isActive }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          Show
        </button>
      )}
    </section>
  );
}

Попробуйте изменить жёстко закодированные значения isActive в компоненте Accordion и посмотрите результат на экране.

Шаг 3: Добавьте состояние общему родителю

Подъём состояния вверх часто меняет характер того, что вы храните как состояние.

В этом случае только одна панель должна быть активна в любой момент. Это означает, что родительский компонент Accordion должен отслеживать, какая панель активна. Вместо значения boolean, он может использовать число как индекс активной Panel для переменной состояния:

const [activeIndex, setActiveIndex] = useState(0);

Когда activeIndex равно 0, активной является первая панель, а когда равно 1, активной является вторая.

Нажатие кнопки «Показать» в любом Panel компоненте должно изменить индекс активности в Accordion. Компонент Panel не может напрямую установить состояние activeIndex, так как оно определено внутри Accordion компонента. Компоненту Accordion необходимо явным образом разрешить компоненту Panel изменять своё состояние, передав обработчик события в качестве свойства:

<>
  <Panel
    isActive={activeIndex === 0}
    onShow={() => setActiveIndex(0)}
  >
    ...
  </Panel>
  <Panel
    isActive={activeIndex === 1}
    onShow={() => setActiveIndex(1)}
  >
    ...
  </Panel>
</>

Компонент <button> внутри Panel теперь будет использовать свойство onShow как свой обработчик события клика:

import { useState } from 'react';

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);
  return (
    <>
      <h2>Almaty, Kazakhstan</h2>
      <Panel
        title="About"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
      </Panel>
      <Panel
        title="Etymology"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
      </Panel>
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}>
          Show
        </button>
      )}
    </section>
  );
}

Это завершает подъём состояния вверх! Перемещение состояния в родительский компонент позволило вам координировать две панели. Использование индекса активности вместо двух флагов «отображается» гарантировало, что активна только одна панель. А передача обработчика события дочернему компоненту позволила ему изменить состояние родительского компонента.

Diagram showing a tree of three components, one parent labeled Accordion and two children labeled Panel. Accordion contains an activeIndex value of zero which turns into isActive value of true passed to the first Panel, and isActive value of false passed to the second Panel.

Изначально Accordion activeIndex имеет значение 0, поэтому первый Panel получает isActive = true

The same diagram as the previous, with the activeIndex value of the parent Accordion component highlighted indicating a click with the value changed to one. The flow to both of the children Panel components is also highlighted, and the isActive value passed to each child is set to the opposite: false for the first Panel and true for the second one.

Когда состояние Accordion activeIndex изменяется на 1, второй Panel получает isActive = true вместо этого

Подробное изучение

Управляемые и неуправляемые компоненты

Обычно компонент с локальным состоянием называют «неуправляемым». Например, исходный компонент Panel с переменной состояния isActive является неуправляемым, потому что его родитель не может повлиять на то, активна ли панель или нет.

Напротив, вы можете сказать, что компонент является «управляемым», когда важная информация в нём управляется свойствами, а не своим локальным состоянием. Это позволяет родительскому компоненту полностью указать его поведение. Конечный компонент Panel со свойством isActive управляется компонентом Accordion.

Неуправляемые компоненты проще использовать в своих родителях, потому что они требуют меньше настроек. Но они менее гибкие, когда вы хотите их координировать. Управляемые компоненты максимально гибкие, но требуют, чтобы родительские компоненты полностью настроили их с помощью свойств.

На практике «управляемые» и «неуправляемые» — это не строгие технические термины — каждый компонент обычно имеет некоторую смесь локального состояния и свойств. Однако это полезный способ обсуждения того, как спроектированы компоненты и какие возможности они предоставляют.

При написании компонента подумайте, какая информация в нём должна управляться (через свойства), а какая — быть неуправляемой (через состояние). Но вы всегда можете передумать и переписать позже.

Единственный источник истины для каждого состояния

Во многих приложениях React у многих компонентов будет своё состояние. Некоторые состояния могут «жить» близко к листовым компонентам (компонентам в нижней части дерева), например, поля ввода. Другие состояния могут «жить» ближе к верху приложения. Например, даже библиотеки клиентской маршрутизации обычно реализуются путём хранения текущего маршрута в состоянии React и передачи его через свойства!

Для каждого уникального фрагмента состояния вы выберете компонент, который «владеет» им. Этот принцип также известен как наличие «единственного источника истины». Это не означает, что всё состояние хранится в одном месте — но что для каждого фрагмента состояния существует конкретный компонент, который хранит эту информацию. Вместо того, чтобы дублировать общее состояние между компонентами, поднимите его вверх до их общего родителя и передайте вниз дочерним компонентам, которым это необходимо.

Ваше приложение будет изменяться по мере работы над ним. Часто вы будете перемещать состояние вниз или вверх, пока не поймёте, где «живёт» каждый фрагмент состояния. Это всё часть процесса!

Чтобы увидеть, как это работает на практике с несколькими компонентами, прочитайте Thinking in React.

Резюме

  • Когда вам нужно координировать два компонента, переместите их состояние в их общего родителя.
  • Затем передайте информацию вниз через свойства от их общего родителя.
  • Наконец, передайте обработчики событий вниз, чтобы дочерние компоненты могли изменить состояние родительского компонента.
  • Полезно рассматривать компоненты как «управляемые» (управляемые свойствами) или «неуправляемые» (управляемые состоянием).

Попробуйте выполнить некоторые задачи

Задача 1 из 2:
Синхронизированные поля ввода

Эти два поля ввода независимы. Сделайте их синхронными: изменение одного поля ввода должно обновлять другое поле ввода тем же текстом, и наоборот.

import { useState } from 'react';

export default function SyncedInputs() {
  return (
    <>
      <Input label="First input" />
      <Input label="Second input" />
    </>
  );
}

function Input({ label }) {
  const [text, setText] = useState('');

  function handleChange(e) {
    setText(e.target.value);
  }

  return (
    <label>
      {label}
      {' '}
      <input
        value={text}
        onChange={handleChange}
      />
    </label>
  );
}

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://react.dev/learn/sharing-state-between-components

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API