Spec-Zone.ru › React

Состояние: Память компонента

Компоненты часто нуждаются в изменении отображаемой на экране информации в результате взаимодействия. Ввод текста в форму должен обновлять поле ввода, нажатие на «следующую» картинку в карусели изображений должно изменять отображаемую картинку, нажатие на «купить» должно добавлять продукт в корзину. Компонентам нужно «запоминать» вещи: текущее значение ввода, текущее изображение, корзину покупок. В React такая специфическая память компонента называется состоянием.

Вы узнаете

  • Как добавить переменную состояния с помощью хука useState
  • Какие два значения возвращает хук useState
  • Как добавить более одной переменной состояния
  • Почему состояние называется локальным

Когда обычной переменной недостаточно

Вот компонент, отображающий изображение скульптуры. Нажатие на кнопку «Далее» должно отображать следующую скульптуру, изменяя index на 1, затем 2, и так далее. Однако это не сработает (вы можете это проверить!):

import { sculptureList } from './data.js';

export default function Gallery() {
  let index = 0;

  function handleClick() {
    index = index + 1;
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Обработчик события handleClick обновляет локальную переменную index. Но две вещи препятствуют отображению этого изменения:

  1. Локальные переменные не сохраняются между рендерами. Когда React повторно рендерит этот компонент, он делает это с нуля — он не учитывает какие-либо изменения в локальных переменных.
  2. Изменения в локальных переменных не вызовут рендер. React не понимает, что ему нужно повторно рендерить компонент с новыми данными.

Для обновления компонента новыми данными необходимо выполнить два действия:

  1. Сохранить данные между рендерами.
  2. Спровоцировать React на повторный рендер компонента с новыми данными (перерендеринг).

Хук useState предоставляет эти два действия:

  1. Переменную состояния для сохранения данных между рендерами.
  2. Функцию установки состояния для обновления переменной и запуска повторного рендеринга компонента React.

Добавление переменной состояния

Для добавления переменной состояния импортируйте useState из React в верхней части файла:

import { useState } from 'react';

Затем замените эту строку:

let index = 0;

на

const [index, setIndex] = useState(0);

index — это переменная состояния, а setIndex — функция установки.

Синтаксис [ и ] здесь называется разрушающим присваиванием, и он позволяет вам считывать значения из массива. Массив, возвращаемый useState, всегда имеет ровно два элемента.

Вот как они работают вместе в handleClick:

function handleClick() {
  setIndex(index + 1);
}

Теперь нажатие на кнопку «Далее» переключает текущую скульптуру:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);

  function handleClick() {
    setIndex(index + 1);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
      <p>
        {sculpture.description}
      </p>
    </>
  );
}

Ваши первые хуки

В React useState, а также любые другие функции, начинающиеся с «use», называются хуками.

Хуки — это специальные функции, доступные только во время рендеринга React (что мы рассмотрим подробнее на следующей странице). Они позволяют «подключаться» к различным функциям React.

Состояние — лишь одна из этих функций, но вы познакомитесь с другими хуками позже.

Подвох

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

Анатомия useState

Когда вы вызываете useState, вы сообщаете React, что хотите, чтобы этот компонент что-то запоминал:

const [index, setIndex] = useState(0);

В этом случае вы хотите, чтобы React запоминал index.

Примечание

Принято называть эту пару, как const [something, setSomething]. Вы можете назвать её как угодно, но соглашения делают вещи более понятными в проектах.

Единственным аргументом для useState является начальное значение вашей переменной состояния. В этом примере начальное значение index установлено в 0 с помощью useState(0).

Каждый раз, когда ваш компонент рендерится, useState возвращает массив, содержащий два значения:

  1. Переменная состояния (index) сохранённым значением.
  2. Функция установки состояния (setIndex) которая может обновить переменную состояния и запустить повторный рендер компонента React.

Вот как это происходит на практике:

const [index, setIndex] = useState(0);
  1. Ваш компонент рендерится в первый раз. Поскольку вы передали 0 в useState в качестве начального значения для index, он вернёт [0, setIndex]. React запоминает, что 0 — последнее значение состояния.
  2. Вы обновляете состояние. Когда пользователь нажимает на кнопку, он вызывает setIndex(index + 1). index равно 0, так что это setIndex(1). Это сообщает React, что теперь index равно 1 и запускает новый рендер.
  3. Второй рендер вашего компонента. React по-прежнему видит useState(0), но поскольку React запоминает, что вы установили index в 1, он возвращает [1, setIndex] вместо этого.
  4. И так далее!

Добавление нескольким компонентам переменных состояния

Вы можете иметь любое количество переменных состояния и любого типа в одном компоненте. Этот компонент имеет две переменные состояния: число index и булево значение showMore, которое переключается при нажатии на «Показать подробности»:

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}

Иметь несколько переменных состояния — хорошая идея, если их состояние не связано, как index и showMore в этом примере. Но если вы часто изменяете две переменные состояния вместе, может быть проще объединить их в одну. Например, если у вас есть форма с множеством полей, удобнее иметь одну переменную состояния, которая содержит объект, а не по отдельности переменную состояния для каждого поля. Подробнее о выборе структуры состояния читайте в разделе Выбор структуры состояния.

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

Как React определяет, какое состояние вернуть?

Вы, возможно, заметили, что вызов useState не получает никакой информации о каком состоянии он относится. Нет «идентификатора», передаваемого в useState, так как он знает, какое из переменных состояния вернуть? Использует ли он какую-то магию, например, разбор ваших функций? Ответ нет.

Вместо этого, для обеспечения лаконичного синтаксиса, хуки полагаются на стабильный порядок вызова на каждом рендере одного и того же компонента. Это работает хорошо на практике, потому что если вы соблюдаете правило выше («вызывать хуки только на верхнем уровне»), хуки всегда будут вызываться в одном и том же порядке. Кроме того, плагин линтера ловит большинство ошибок.

Внутри React хранит массив пар состояний для каждого компонента. Он также поддерживает текущий индекс пары, который установлен в 0 перед рендерингом. Каждый раз, когда вы вызываете useState, React даёт вам следующую пару состояний и увеличивает индекс. Более подробно об этом механизме вы можете прочитать в React Hooks: Not Magic, Just Arrays.

Этот пример не использует React, но даёт представление о том, как useState работает внутри:

let componentHooks = [];
let currentHookIndex = 0;

// How useState works inside React (simplified).
function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // This is not the first render,
    // so the state pair already exists.
    // Return it and prepare for next Hook call.
    currentHookIndex++;
    return pair;
  }

  // This is the first time we're rendering,
  // so create a state pair and store it.
  pair = [initialState, setState];

  function setState(nextState) {
    // When the user requests a state change,
    // put the new value into the pair.
    pair[0] = nextState;
    updateDOM();
  }

  // Store the pair for future renders
  // and prepare for the next Hook call.
  componentHooks[currentHookIndex] = pair;
  currentHookIndex++;
  return pair;
}

function Gallery() {
  // Each useState() call will get the next pair.
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  // This example doesn't use React, so
  // return an output object instead of JSX.
  return {
    onNextClick: handleNextClick,
    onMoreClick: handleMoreClick,
    header: `${sculpture.name} by ${sculpture.artist}`,
    counter: `${index + 1} of ${sculptureList.length}`,
    more: `${showMore ? 'Hide' : 'Show'} details`,
    description: showMore ? sculpture.description : null,
    imageSrc: sculpture.url,
    imageAlt: sculpture.alt
  };
}

function updateDOM() {
  // Reset the current Hook index
  // before rendering the component.
  currentHookIndex = 0;
  let output = Gallery();

  // Update the DOM to match the output.
  // This is the part React does for you.
  nextButton.onclick = output.onNextClick;
  header.textContent = output.header;
  moreButton.onclick = output.onMoreClick;
  moreButton.textContent = output.more;
  image.src = output.imageSrc;
  image.alt = output.imageAlt;
  if (output.description !== null) {
    description.textContent = output.description;
    description.style.display = '';
  } else {
    description.style.display = 'none';
  }
}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
let description = document.getElementById('description');
let image = document.getElementById('image');
let sculptureList = [{
  name: 'Homenaje a la Neurocirugía',
  artist: 'Marta Colvin Andrade',
  description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
  url: 'https://i.imgur.com/Mx7dA2Y.jpg',
  alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'  
}, {
  name: 'Floralis Genérica',
  artist: 'Eduardo Catalano',
  description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.',
  url: 'https://i.imgur.com/ZF6s192m.jpg',
  alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.'
}, {
  name: 'Eternal Presence',
  artist: 'John Woodrow Wilson',
  description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
  url: 'https://i.imgur.com/aTtVpES.jpg',
  alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.'
}, {
  name: 'Moai',
  artist: 'Unknown Artist',
  description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.',
  url: 'https://i.imgur.com/RCwLEoQm.jpg',
  alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.'
}, {
  name: 'Blue Nana',
  artist: 'Niki de Saint Phalle',
  description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.',
  url: 'https://i.imgur.com/Sd1AgUOm.jpg',
  alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.'
}, {
  name: 'Ultimate Form',
  artist: 'Barbara Hepworth',
  description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.',
  url: 'https://i.imgur.com/2heNQDcm.jpg',
  alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.'
}, {
  name: 'Cavaliere',
  artist: 'Lamidi Olonade Fakeye',
  description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
  url: 'https://i.imgur.com/wIdGuZwm.png',
  alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.'
}, {
  name: 'Big Bellies',
  artist: 'Alina Szapocznikow',
  description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
  url: 'https://i.imgur.com/AlHTAdDm.jpg',
  alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.'
}, {
  name: 'Terracotta Army',
  artist: 'Unknown Artist',
  description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.',
  url: 'https://i.imgur.com/HMFmH6m.jpg',
  alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.'
}, {
  name: 'Lunar Landscape',
  artist: 'Louise Nevelson',
  description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.',
  url: 'https://i.imgur.com/rN7hY6om.jpg',
  alt: 'A black matte sculpture where the individual elements are initially indistinguishable.'
}, {
  name: 'Aureole',
  artist: 'Ranjani Shettar',
  description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
  url: 'https://i.imgur.com/okTpbHhm.jpg',
  alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.'
}, {
  name: 'Hippos',
  artist: 'Taipei Zoo',
  description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.',
  url: 'https://i.imgur.com/6o5Vuyu.jpg',
  alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.'
}];

// Make UI match the initial state.
updateDOM();

Вам не нужно это понимать, чтобы использовать React, но вы можете найти это полезной ментальной моделью.

Состояние изолировано и закрыто

Состояние локально для экземпляра компонента на экране. Другими словами, если вы рендерите один и тот же компонент дважды, каждый экземпляр будет иметь совершенно изолированное состояние! Изменение одного из них не повлияет на другой.

В этом примере компонент Gallery из предыдущего примера рендерится дважды без изменений в его логике. Попробуйте нажать на кнопки внутри каждой галереи. Обратите внимание, что их состояния независимы:

import Gallery from './Gallery.js';

export default function Page() {
  return (
    <div className="Page">
      <Gallery />
      <Gallery />
    </div>
  );
}

Это отличает состояние от обычных переменных, которые вы можете объявить в начале своего модуля. Состояние не привязано к конкретному вызову функции или месту в коде, а является «локальным» для конкретного места на экране. Вы рендерили два компонента <Gallery /> , поэтому их состояние хранится отдельно.

Также обратите внимание, как компонент Page ничего не «знает» о состоянии Gallery или даже о том, есть ли у него состояние. В отличие от свойств, состояние полностью приватное для компонента, который его объявляет. Родительский компонент не может его изменить. Это позволяет вам добавлять состояние в любой компонент или удалять его без влияния на остальные компоненты.

А что, если вам нужно, чтобы обе галереи синхронизировали свое состояние? Правильный способ сделать это в React — удалить состояние из дочерних компонентов и добавить его к ближайшему общему родительскому компоненту. На следующих страницах мы сосредоточимся на организации состояния одного компонента, но мы вернемся к этой теме в Обмен состоянием между компонентами.

Резюме

  • Используйте переменную состояния, когда компоненту нужно «запомнить» какую-либо информацию между рендерами.
  • Переменные состояния объявляются путём вызова хука useState.
  • Хуки — это специальные функции, которые начинаются с use. Они позволяют «подключаться» к функциям React, таким как состояние.
  • Хуки могут напомнить вам об импортах: их нужно вызывать безусловно. Вызов хуков, включая useState, допустим только на верхнем уровне компонента или другого хука.
  • Хук useState возвращает пару значений: текущее состояние и функцию его обновления.
  • Вы можете иметь более одной переменной состояния. Внутри React они сопоставляются в соответствии с их порядком.
  • Состояние приватное для компонента. Если вы рендерите его в двух местах, каждый экземпляр получает свое состояние.

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

Задание 1 из 4:
Заполните галерею

При нажатии на «Далее» на последней скульптуре код аварийно завершается. Исправьте логику, чтобы предотвратить сбой. Вы можете сделать это, добавив дополнительную логику в обработчик событий или отключив кнопку, когда действие невозможно.

После исправления сбоя добавьте кнопку «Назад», которая отображает предыдущую скульптуру. Она не должна аварийно завершаться на первой скульптуре.

import { useState } from 'react';
import { sculptureList } from './data.js';

export default function Gallery() {
  const [index, setIndex] = useState(0);
  const [showMore, setShowMore] = useState(false);

  function handleNextClick() {
    setIndex(index + 1);
  }

  function handleMoreClick() {
    setShowMore(!showMore);
  }

  let sculpture = sculptureList[index];
  return (
    <>
      <button onClick={handleNextClick}>
        Next
      </button>
      <h2>
        <i>{sculpture.name} </i> 
        by {sculpture.artist}
      </h2>
      <h3>  
        ({index + 1} of {sculptureList.length})
      </h3>
      <button onClick={handleMoreClick}>
        {showMore ? 'Hide' : 'Show'} details
      </button>
      {showMore && <p>{sculpture.description}</p>}
      <img 
        src={sculpture.url} 
        alt={sculpture.alt}
      />
    </>
  );
}

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

Spec-Zone.ru

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