Спільний доступ до стану між компонентами

Іноді вам потрібно, щоб стан двох компонент завжди змінювався разом. Щоб так зробити, видаліть стан з обох компонент, перенесіть його до їхнього найближчого спільного батька та потім передайте стан вниз до них завдяки пропсам. Це відоме як підняття стану вгору і це одна з найпоширеніших речей, які ви будете робити під час написання React коду.

You will learn

  • Як поширювати стан між компонент підняттям його вгору
  • Що таке контрольовані та не контрольовані компоненти

Підняття стану на прикладі

В цьому прикладі, батьківський компонент 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)}>
          Показати
        </button>
      )}
    </section>
  );
}

export default function Accordion() {
  return (
    <>
      <h2>Алмати, Казахстан</h2>
      <Panel title="Про Алмати">
        Із населенням близько 2 мільйонів, Алмати є найбільшим містом в Казахстані. З 1929 по 1997, воно було його столицею.
      </Panel>
      <Panel title="Етимологія">
        Назва походить від казахського слова <span lang="kk-KZ">алма</span>, що означає "яблуко" і часто перекладалось як "повний яблук". Насправді, регіон що оточує Алмати вважається прабатьківщиною яблука, і дика <i lang="la">Malus sieversii</i> вважається ймовірним кандидатом на предка сучасного домашнього яблука.
      </Panel>
    </>
  );
}

Зверніть увагу, що натискання кнопки однієї панелі не впливає на іншу панель—вони незалежні.

Діаграма показує дерево з трьох компонент, один батько з назвою Accordion і два дочірні компоненти з назвами Panel. Обидва компоненти Panel містять isActive зі значенням false.
Діаграма показує дерево з трьох компонент, один батько з назвою Accordion і два дочірні компоненти з назвами Panel. Обидва компоненти Panel містять isActive зі значенням false.

Спочатку стан isActive кожної Panel дорівнює false, тому вони обидві виглядають згорнутими

Та сама діаграма, що й попередня, з виділеним isActive першого дочірнього компонента Panel, що вказує на клік зі значенням isActive, встановленим у true. Другий компонент Panel все ще містить значення false.
Та сама діаграма, що й попередня, з виділеним isActive першого дочірнього компонента Panel, що вказує на клік зі значенням isActive, встановленим у true. Другий компонент Panel все ще містить значення false.

Натискання на будь-яку з кнопок Panel призведе до оновлення стану isActive тільки цієї Panel.

Але тепер припустимо, що ви хочете змінити це так, щоб тільки одна панель була розгорнути в будь-який момент часу. При такому дизайні, розгортання другої панелі повинно згорнути першу. Як би ви це зробили?

Щоб скоординувати ці дві панелі, вам потрібно “підняти їхній стан вгору” до батьківського компонента в три кроки:

  1. Видалити стан із дочірніх компонент.
  2. Передати жорсткокодовані дані від спільного батька.
  3. Додати стан до спільного батька і передати його вниз разом з обробниками подій.

Це дозволить компоненту Accordionскоординувати обидві Panel та розгортати тільки одну на раз.

Крок 1: Видаліть стан з дочірніх компонент

Ви передасте контроль isActive Panel до її батьківської компоненти. Це означає що батьківський компоненнт передасть 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>Алмати, Казахстан</h2>
      <Panel title="Про Алмати" isActive={true}>
        Із населенням близько 2 мільйонів, Алмати є найбільшим містом в Казахстані. З 1929 по 1997, воно було його столицею.
      </Panel>
      <Panel title="Етимологія" isActive={true}>
        Назва походить від казахського слова <span lang="kk-KZ">алма</span>, що означає "яблуко" і часто перекладалось як "повний яблук". Насправді, регіон що оточує Алмати вважається прабатьківщиною яблука, і дика <i lang="la">Malus sieversii</i> вважається ймовірним кандидатом на предка сучасного домашнього яблука.
      </Panel>
    </>
  );
}

function Panel({ title, children, isActive }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={() => setIsActive(true)}>
          Показати
        </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>Алмати, Казахстан</h2>
       <Panel
        title="Про Алмати"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        Із населенням близько 2 мільйонів, Алмати є найбільшим містом в Казахстані. З 1929 по 1997, воно було його столицею.
      </Panel>
      <Panel
        title="Етимологія"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        Назва походить від казахського слова <span lang="kk-KZ">алма</span>, що означає "яблуко" і часто перекладалось як "повний яблук". Насправді, регіон що оточує Алмати вважається прабатьківщиною яблука, і дика <i lang="la">Malus sieversii</i> вважається ймовірним кандидатом на предка сучасного домашнього яблука.
      </Panel>
    </>
  );
}

function Panel({
  title,
  children,
  isActive,
  onShow
}) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        <p>{children}</p>
      ) : (
        <button onClick={onShow}>
          Показати
        </button>
      )}
    </section>
  );
}

Підняття стану вгору завершено! Переміщення стану в спільний батьківський компонент дозволяє вам скоординувати дві панелі. Використовуючи активний індекс замість двох “активно” прапорців гарантує, що тільки одна панель буде активною на даний момент. І передаючи вниз обробник подій до дочірньої компоненти дозволить йому змінювати батьківський стан.

Діаграма зоображає дерево із трьох компонент, один батьківський з назвою Accordion та двох дочірніх з назвами Panel. Accordion містить activeIndex із нульовим значенням, яке стає isActive із значенням true, що передається до першої панелі та isActive із значенням false до другої Panel.
Діаграма зоображає дерево із трьох компонент, один батьківський з назвою Accordion та двох дочірніх з назвами Panel. Accordion містить activeIndex із нульовим значенням, яке стає isActive із значенням true, що передається до першої панелі та isActive із значенням false до другої Panel.

Початково, Accordion має activeIndex із значенням 0, тому перша Panel отримує isActive = true

Така сама діаграма що й попередня, тільки activeIndex значення батьківського компонента, яке підсвічено, що вказує на клік із значенням зміненим на одиницю. Шлях до обох дочірніх Panel компонент таокж підсвічений та isActive значення, яке передане до кожної дочірньої компоненти є протилежним: false для першої Panel та true для другої
Така сама діаграма що й попередня, тільки activeIndex значення батьківського компонента, яке підсвічено, що вказує на клік із значенням зміненим на одиницю. Шлях до обох дочірніх Panel компонент таокж підсвічений та isActive значення, яке передане до кожної дочірньої компоненти є протилежним: false для першої Panel та true для другої

Коли стан Accordion activeIndex змінюється на 1, друга Panel отримує isActive = true

Deep Dive

Контрольовані та неконтрольовані компоненти

Прийнято називати деякий компонент із локальним станом “неконтрольованим”. Для прикладу, вихідний Panel компонент із isActive змінною стану є неконтрольований, оскільки його батько не може впливати на те, чи буде панель активною чи ні.

На противагу, ви можете сказати що компонент є “контрольований” коли важлива інформація в ньому керується за допомогою пропсів а не його власним локальним станом. Це дозволяє батьківському компоненту повністю визанчити його поведінку. Останній Panel компонент із isActive проп є контрольований компонентом Accordion.

Неконтрольовані компоненти легше використовувати із їхніми батьківськими, оскільки вони вимагають менше налаштувань. Але вони менш гнучкі, коли ви хочете скоординувати їх разом. Контрольовані компоненти є максимально гнучкими але вони вимагають від батьківських компонент повністю налаштувати їх за допомогою пропсів.

На практиці, “контрольований” та “неконтрольований” не є строгими технічними термінами—кожен компонент зазвичай має певну суміш як локального стану та пропсів. Однак, це корисний спосіб, розповісти про те, як компоненти розроблені та які можливості вони пропонують.

Коли пишите компонент, подумайте, яка інформація в ньому має бути контрольованою (за допомогою пропсів), а яка інформація має бути неконтрольована (за допомогою стану). Але завжди можете передумати і змінити це пізніше.

Єдине джерело правди для кожного стану

В React застосунку, багато компонент матимуть їхній власний стан. Деякий стан може “жити” близько до листкових компонент (компоненти, що знаходяться внизу дерева), для прикладу, поля вводу. Інші стани можуть “жити” ближче до вершини застосунку. Для прикладу, навіть клієнтські бібліотеки маршрутизації зазвичай реалізовані за допомогою зберігання поточного маршруту в React стані та передають його вниз за допомогою пропсів!

Для кожної унікальної частинки стану, ви оберете компонент що “належить” йому. Цей принцип також відомий як “єдине джерело правди” Це не означає, що весь стан знаходиться в одному місці—це означає, що для кожної частини стану існує певний компонент, який містить ту частину інформації. Замість дублювання спільного стану між компонентами, підніміть його вгору до їхнього спільного батька та передайте його вниз до дочірніх компонент, де він потрібний.

Ваш застосунок буде змінюватись в міру того, як ви працюєте над ним. Часто буває так. що ви переміщуєте стан вниз або назад вгору, протягом того, як стараєтесь з’ясувати де кожен шматок стану “живе”. Це все частина процесу!

Щоб побачити, як це виглядає на практиці з кількома іншими компонентами, читайте Мислення в React.

Recap

  • Коли ви хочете скоординувати два компоненти, перенесіть їхній стан до їхнього спільного батька.
  • Потім передайте інформацію вниз через пропси від їхнього спільного батька.
  • Нарешті, передайте обробник подій вниз, щоб діти могли змінювати батьківський стан.
  • Корисно розглядати компонент як “контрольований” (керований пропсами) або “неконтрольований” (керований станом).

Challenge 1 of 2:
Синхронізовані поля вводу

Ці два поля вводу є незалежні. Зробіть так, щоб вони були синхронізовані: редагування одного поля вводу має оновлювати інше поле вводу із тим самим текстом, і навпаки.

import { useState } from 'react';

export default function SyncedInputs() {
  return (
    <>
      <Input label="Перше поле вводу" />
      <Input label="Друге поле вводу" />
    </>
  );
}

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

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

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