Spec-Zone.ru › React 18

Сохранение и сброс состояния

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

Вы узнаете

  • Когда React выбирает сохранять или сбрасывать состояние
  • Как заставить React сбросить состояние компонента
  • Как ключи и типы влияют на сохранение состояния

Состояние привязано к позиции в дереве рендеринга

React строит деревья рендеринга для структуры компонентов в вашем пользовательском интерфейсе.

Когда вы предоставляете компоненту состояние, вы можете подумать, что состояние «живет» внутри компонента. Но состояние фактически хранится внутри React. React связывает каждое хранимое им состояние с соответствующим компонентом в зависимости от того, где этот компонент находится в дереве рендеринга.

Здесь есть только один <Counter /> тег JSX, но он отображается в двух разных позициях:

import { useState } from 'react';

export default function App() {
  const counter = <Counter />;
  return (
    <div>
      {counter}
      {counter}
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Вот как это выглядит в виде дерева:

Diagram of a tree of React components. The root node is labeled 'div' and has two children. Each of the children are labeled 'Counter' and both contain a state bubble labeled 'count' with value 0.

Дерево React

Это два отдельных счётчика, поскольку каждый из них отображается в своей позиции в дереве. Обычно вам не нужно думать об этих позициях, чтобы использовать React, но это может быть полезно, чтобы понять, как он работает.

В React каждый компонент на экране имеет полностью изолированное состояние. Например, если вы отображаете два Counter компонента рядом, каждый из них получит своё собственное, независимое, score и hover состояния.

Попробуйте нажать на оба счётчика и обратите внимание, что они не влияют друг на друга:

import { useState } from 'react';

export default function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Как видите, при обновлении одного счётчика обновляется только состояние этого компонента:

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 1. The state bubble of the right child is highlighted in yellow to indicate its value has updated.

Обновление состояния

React будет хранить состояние до тех пор, пока вы отображаете тот же компонент в той же позиции в дереве. Чтобы увидеть это, увеличьте оба счётчика, затем удалите второй компонент, сняв флажок «Отобразить второй счётчик», а затем добавьте его обратно, установив флажок:

import { useState } from 'react';

export default function App() {
  const [showB, setShowB] = useState(true);
  return (
    <div>
      <Counter />
      {showB && <Counter />} 
      <label>
        <input
          type="checkbox"
          checked={showB}
          onChange={e => {
            setShowB(e.target.checked)
          }}
        />
        Render the second counter
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Обратите внимание, как в момент остановки рендеринга второго счётчика его состояние полностью исчезает. Это происходит потому, что при удалении React компонента он уничтожает его состояние.

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is missing, and in its place is a yellow 'poof' image, highlighting the component being deleted from the tree.

Удаление компонента

Когда вы устанавливаете флажок «Отобразить второй счётчик», второй Counter и его состояние инициализируются заново (score = 0) и добавляются в DOM.

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The entire right child node is highlighted in yellow, indicating that it was just added to the tree.

Добавление компонента

React сохраняет состояние компонента до тех пор, пока он отображается в своей позиции в дереве пользовательского интерфейса. Если его удаляют или в ту же позицию отображается другой компонент, React уничтожает его состояние.

Сохранение состояния при одинаковом компоненте в той же позиции

В этом примере есть два разных <Counter /> тега:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

При установке или снятии флажка состояние счётчика не сбрасывается. Независимо от того, isFancy true или false, у вас всегда будет <Counter /> в качестве первого дочернего элемента div возвращаемого корневым App компонентом:

Diagram with two sections separated by an arrow transitioning between them. Each section contains a layout of components with a parent labeled 'App' containing a state bubble labeled isFancy. This component has one child labeled 'div', which leads to a prop bubble containing isFancy (highlighted in purple) passed down to the only child. The last child is labeled 'Counter' and contains a state bubble with label 'count' and value 3 in both diagrams. In the left section of the diagram, nothing is highlighted and the isFancy parent state value is false. In the right section of the diagram, the isFancy parent state value has changed to true and it is highlighted in yellow, and so is the props bubble below, which has also changed its isFancy value to true.

Обновление состояния App не сбрасывает состояние Counter, потому что Counter остаётся в той же позиции

Это тот же компонент в той же позиции, поэтому с точки зрения React это тот же счётчик.

Опасная ситуация

Помните, что для React важна позиция в дереве пользовательского интерфейса, а не в разметке JSX! Этот компонент имеет два return условия с разными <Counter /> тегами JSX внутри и снаружи if:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  if (isFancy) {
    return (
      <div>
        <Counter isFancy={true} />
        <label>
          <input
            type="checkbox"
            checked={isFancy}
            onChange={e => {
              setIsFancy(e.target.checked)
            }}
          />
          Use fancy styling
        </label>
      </div>
    );
  }
  return (
    <div>
      <Counter isFancy={false} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

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

В обоих случаях компонент App возвращает <div> с <Counter /> в качестве первого дочернего элемента. Для React эти два счётчика имеют один и тот же «адрес»: первый дочерний элемент первого дочернего элемента корня. Так React сопоставляет их между предыдущими и последующими рендерами, независимо от того, как вы структурируете свою логику.

Разные компоненты в одной позиции сбрасывают состояние

В этом примере при установке флажка <Counter> будет заменён на <p>:

import { useState } from 'react';

export default function App() {
  const [isPaused, setIsPaused] = useState(false);
  return (
    <div>
      {isPaused ? (
        <p>See you later!</p> 
      ) : (
        <Counter /> 
      )}
      <label>
        <input
          type="checkbox"
          checked={isPaused}
          onChange={e => {
            setIsPaused(e.target.checked)
          }}
        />
        Take a break
      </label>
    </div>
  );
}

function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Здесь вы переключаетесь между разными типами компонентов в одной позиции. Изначально первый дочерний элемент <div> содержал Counter. Но когда вы заменили его на p, React удалил Counter из дерева пользовательского интерфейса и уничтожил его состояние.

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'p', highlighted in yellow.

При изменении Counter на p, Counter удаляется, и p добавляется

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'p'. The middle section has the same 'div' parent, but the child component has now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, highlighted in yellow.

При возвращении к предыдущему, p удаляется, и Counter добавляется

Также, при рендеринге другого компонента в той же позиции, он сбрасывает состояние всего своего поддерева. Чтобы увидеть, как это работает, увеличьте счётчик, а затем установите флажок:

import { useState } from 'react';

export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
    <div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} /> 
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
          }}
        />
        Use fancy styling
      </label>
    </div>
  );
}

function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }
  if (isFancy) {
    className += ' fancy';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Состояние счётчика сбрасывается при нажатии на флажок. Хотя вы рендерите Counter, первый дочерний элемент div изменяется с div на section. Когда дочерний элемент div был удалён из DOM, всё поддерево ниже него (включая Counter и его состояние) также было уничтожено.

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'section', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 3. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'div', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow.

При изменении section на div, section удаляется, и новый div добавляется

Diagram with three sections, with an arrow transitioning each section in between. The first section contains a React component labeled 'div' with a single child labeled 'div', which has a single child labeled 'Counter' containing a state bubble labeled 'count' with value 0. The middle section has the same 'div' parent, but the child components have now been deleted, indicated by a yellow 'proof' image. The third section has the same 'div' parent again, now with a new child labeled 'section', highlighted in yellow, also with a new child labeled 'Counter' containing a state bubble labeled 'count' with value 0, all highlighted in yellow.

При возвращении к предыдущему, div удаляется, и новый section добавляется

Как правило, если вы хотите сохранить состояние между перерисовками, структура вашего дерева должна «совпадать» от одной перерисовки к другой. Если структура отличается, состояние уничтожается, потому что React уничтожает состояние при удалении компонента из дерева.

Опасная ситуация

Вот почему вы не должны вкладывать определения функций компонентов.

Здесь функция компонента MyTextField определена внутри MyComponent:

import { useState } from 'react';

export default function MyComponent() {
  const [counter, setCounter] = useState(0);

  function MyTextField() {
    const [text, setText] = useState('');

    return (
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
    );
  }

  return (
    <>
      <MyTextField />
      <button onClick={() => {
        setCounter(counter + 1)
      }}>Clicked {counter} times</button>
    </>
  );
}

Каждый раз, когда вы нажимаете кнопку, состояние ввода исчезает! Это происходит потому, что для каждой перерисовки MyComponent создаётся новая функция компонента MyTextField. Вы отображаете другой компонент в одной и той же позиции, поэтому React сбрасывает всё состояние ниже. Это приводит к ошибкам и проблемам с производительностью. Чтобы избежать этой проблемы, всегда объявляйте функции компонентов на верхнем уровне и не вкладывайте их определения.

Сброс состояния в одной позиции

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

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

В настоящее время при изменении игрока счёт сохраняется. Два Counter отображаются в одной и той же позиции, поэтому React рассматривает их как один и тот же Counter с изменившимся свойством person.

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

Есть два способа сбросить состояние при переключении между ними:

  1. Отображать компоненты в разных позициях
  2. Назначать каждому компоненту явную идентичность с помощью key

Вариант 1: Отображение компонента в разных позициях

Если вы хотите, чтобы эти два Counter были независимыми, вы можете отобразить их в двух разных позициях:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

  • Изначально, isPlayerA true. Таким образом, первая позиция содержит состояние Counter, а вторая пустая.
  • При нажатии на кнопку «Следующий игрок» первая позиция очищается, а вторая теперь содержит Counter.
Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The only child, arranged to the left, is labeled Counter with a state bubble labeled 'count' and value 0. All of the left child is highlighted in yellow, indicating it was added.

Начальное состояние

Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'false'. The state bubble is highlighted in yellow, indicating that it has changed. The left child is replaced with a yellow 'poof' image indicating that it has been deleted and there is a new child on the right, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0.

Нажатие «следующий»

Diagram with a tree of React components. The parent is labeled 'Scoreboard' with a state bubble labeled isPlayerA with value 'true'. The state bubble is highlighted in yellow, indicating that it has changed. There is a new child on the left, highlighted in yellow indicating that it was added. The new child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is replaced with a yellow 'poof' image indicating that it has been deleted.

Нажатие «следующий» ещё раз

Состояние каждого Counter уничтожается каждый раз, когда он удаляется из DOM. Именно поэтому они сбрасываются каждый раз, когда вы нажимаете кнопку.

Это решение удобно, когда у вас отображается всего несколько независимых компонентов в одном месте. В этом примере у вас только два, поэтому отображать их отдельно в JSX несложно.

Вариант 2: Сброс состояния с помощью ключа

Есть и другой, более универсальный способ сбросить состояние компонента.

Вы могли видеть key при отображении списков. Ключи нужны не только для списков! Вы можете использовать ключи, чтобы заставить React различать любые компоненты. По умолчанию React использует порядок внутри родителя («первый счётчик», «второй счётчик»), чтобы различать компоненты. Но ключи позволяют сообщить React, что это не просто первый счётчик или второй счётчик, а конкретный счётчик — например, счётчик Тейлора. Таким образом, React узнает счётчик Тейлора, где бы он ни появлялся в дереве!

В этом примере два <Counter /> не делят состояние, хотя они появляются в одном и том же месте в JSX:

import { useState } from 'react';

export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter key="Taylor" person="Taylor" />
      ) : (
        <Counter key="Sarah" person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        Next player!
      </button>
    </div>
  );
}

function Counter({ person }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);

  let className = 'counter';
  if (hover) {
    className += ' hover';
  }

  return (
    <div
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
    >
      <h1>{person}'s score: {score}</h1>
      <button onClick={() => setScore(score + 1)}>
        Add one
      </button>
    </div>
  );
}

Переключение между Тейлором и Сарой не сохраняет состояние. Это происходит потому, что вы присвоили им разные keys:

{isPlayerA ? (
  <Counter key="Taylor" person="Taylor" />
) : (
  <Counter key="Sarah" person="Sarah" />
)}

Указание key сообщает React использовать само key в качестве части позиции, вместо их порядка в родительском элементе. Именно поэтому, даже если вы отображаете их в одном и том же месте в JSX, React видит их как два разных счётчика, и поэтому они никогда не будут совместно использовать состояние. Каждый раз, когда счётчик появляется на экране, его состояние создаётся. Каждый раз, когда он удаляется, его состояние уничтожается. Переключение между ними постоянно сбрасывает их состояние.

Примечание

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

Сброс формы с ключом

Сброс состояния с ключом особенно полезен при работе с формами.

В этом приложении чата компонент <Chat> содержит состояние текстового поля ввода:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

Попробуйте ввести что-нибудь в поле ввода, а затем нажмите «Элис» или «Боб», чтобы выбрать другого получателя. Вы заметите, что состояние поля ввода сохраняется, потому что компонент <Chat> отображается в той же позиции в дереве.

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

<Chat key={to.id} contact={to} />

Это гарантирует, что при выборе другого получателя компонент Chat будет пересоздан заново, включая любое состояние в поддереве. React также пересоздаст элементы DOM вместо повторного использования.

Теперь при переключении получателя поле ввода всегда очищается:

import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';

export default function Messenger() {
  const [to, setTo] = useState(contacts[0]);
  return (
    <div>
      <ContactList
        contacts={contacts}
        selectedContact={to}
        onSelect={contact => setTo(contact)}
      />
      <Chat key={to.id} contact={to} />
    </div>
  )
}

const contacts = [
  { id: 0, name: 'Taylor', email: 'taylor@mail.com' },
  { id: 1, name: 'Alice', email: 'alice@mail.com' },
  { id: 2, name: 'Bob', email: 'bob@mail.com' }
];

Подробный анализ

Сохранение состояния для удаленных компонентов

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

  • Вы можете отобразить все чаты, а не только текущий, но скрыть все остальные с помощью CSS. Чаты не будут удалены из дерева, поэтому их локальное состояние будет сохранено. Это решение отлично подходит для простых пользовательских интерфейсов. Но оно может стать очень медленным, если скрытые деревья большие и содержат много узлов DOM.
  • Вы можете поднять состояние наверх и хранить ожидаемое сообщение для каждого получателя в родительском компоненте. Таким образом, когда дочерние компоненты удаляются, это не имеет значения, потому что родительский компонент сохраняет важную информацию. Это наиболее распространённое решение.
  • Вы также можете использовать другой источник помимо состояния React. Например, вам, вероятно, нужно, чтобы черновик сообщения сохранялся даже если пользователь случайно закрывает страницу. Для реализации этого вы можете настроить компонент Chat на инициализацию его состояния с помощью считывания из localStorage, и сохранять черновики там же.

Независимо от выбранной стратегии, чат с Элис концептуально отличается от чата с Бобом, поэтому имеет смысл присваивать key дереву <Chat> на основе текущего получателя.

Резюме

  • React сохраняет состояние, пока один и тот же компонент отображается в одном и том же месте.
  • Состояние не сохраняется в тегах JSX. Оно связано с позицией в дереве, в которую вы помещаете этот JSX.
  • Вы можете принудительно сбросить состояние поддерева, присвоив ему другой ключ.
  • Не вкладывайте определения компонентов друг в друга, иначе вы случайно сбросите состояние.

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

Задание 1 из 5:
Исправить исчезновение текста ввода

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

import { useState } from 'react';

export default function App() {
  const [showHint, setShowHint] = useState(false);
  if (showHint) {
    return (
      <div>
        <p><i>Hint: Your favorite city?</i></p>
        <Form />
        <button onClick={() => {
          setShowHint(false);
        }}>Hide hint</button>
      </div>
    );
  }
  return (
    <div>
      <Form />
      <button onClick={() => {
        setShowHint(true);
      }}>Show hint</button>
    </div>
  );
}

function Form() {
  const [text, setText] = useState('');
  return (
    <textarea
      value={text}
      onChange={e => setText(e.target.value)}
    />
  );
}

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/learn/preserving-and-resetting-state

Spec-Zone.ru

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