← Назад к вопросам

Как useState запоминает прошлое состояние?

2.0 Middle🔥 301 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как useState запоминает прошлое состояние в React

useState — это не магия. React имеет чёткий механизм для запоминания состояния каждого компонента. Понимание этого механизма критично для правильного использования хуков.

Основной механизм: Fiber Architecture

React использует структуру данных "Fiber" для каждого компонента:

// Упрощённая структура Fiber
const fiber = {
  component: MyComponent,
  props: { name: "John" },
  state: [],           // Здесь хранятся значения useState
  hooks: [],           // Информация о хуках
  parent: parentFiber,
  child: childFiber,
  next: siblingFiber
};

Каждому компоненту соответствует уникальный fiber узел в дереве. На этом узле хранится:

  • Текущее состояние
  • История предыдущих состояний
  • Информация обо всех хуках

Порядок вызовов хуков (Hook Rules)

Критически важно: useState должен вызываться в одном и том же порядке!

Реакт запоминает состояние на основе порядка вызова хуков, а не их названия:

// ПРАВИЛЬНО — всегда в одном порядке
function Counter() {
  const [count, setCount] = useState(0);      // Hook 0
  const [name, setName] = useState("John");   // Hook 1
  const [email, setEmail] = useState("");     // Hook 2
  // ...
}

// Fiber запоминает:
// fiber.hooks[0] = { state: 0, setState: setCount }
// fiber.hooks[1] = { state: "John", setState: setName }
// fiber.hooks[2] = { state: "", setState: setEmail }

Почему порядок критичен

// ОШИБКА! — нарушает порядок вызовов
function Broken({ shouldUseEmail }) {
  const [count, setCount] = useState(0);
  
  if (shouldUseEmail) {
    // Этот хук может быть пропущен!
    const [email, setEmail] = useState("");
  }
  
  const [name, setName] = useState("John");
  // ^ На этой итерации это станет Hook 1 вместо Hook 2!
  // Результат: name получит значение email из fiber.hooks[1]
}

Как React отслеживает состояние

Когда компонент рендерится:

function MyComponent() {
  // 1. React находит Fiber для MyComponent
  // 2. Выполняет код функции
  // 3. При вызове useState(0):
  const [count, setCount] = useState(0);
  //     React проверяет fiber.hooks[0]
  //     Если первый рендер — создаёт новый Hook object
  //     Если повторный — берёт старое значение
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Замыкание и состояние

setState создаёт замыкание над значением состояния:

function Counter() {
  const [count, setCount] = useState(0);
  
  // setState замкнут на fiber узел этого компонента
  const increment = () => {
    setCount(count + 1);
  };
  
  // Даже если функция передана в другой компонент,
  // она всё ещё знает где её состояние!
  return <Child onClick={increment} />;
}

Функциональное обновление

Иногда нужно обновлять на основе предыдущего состояния:

function Counter() {
  const [count, setCount] = useState(0);
  
  // НЕПРАВИЛЬНО — зависит от замыкания
  const increment = () => setCount(count + 1);
  
  // ПРАВИЛЬНО — React предоставит предыдущее состояние
  const betterIncrement = () => {
    setCount(prevCount => prevCount + 1);
  };
  
  // Особенно важно при асинхронности
  const handleAsyncClick = () => {
    setTimeout(() => {
      setCount(prev => prev + 1);
    }, 1000);
  };
  
  return <button onClick={betterIncrement}>Count: {count}</button>;
}

Batch Updates (пакетные обновления)

React не обновляет состояние сразу:

function Handler() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(1);  // Запланировано
    setCount(2);  // Запланировано
    setCount(3);  // Запланировано
    console.log(count); // Всё ещё 0!
  };
  
  // React объединит все 3 обновления в одно
  // И выполнит только финальный setCount(3)
  // Это повышает производительность!
}

Отслеживание зависимостей

Похожий механизм работает и в useEffect:

function Timer() {
  const [count, setCount] = useState(0);
  
  // React запоминает:
  // fiber.hooks[1] = { 
  //   effect: () => { ... },
  //   deps: [count],  // Запоминает зависимости!
  // }
  useEffect(() => {
    console.log("Count changed to", count);
  }, [count]);
  
  // При следующем рендере React сравнит:
  // была ли deps[0] (count) одинаковой?
  // Если да — эффект не выполняется
  // Если нет — выполняется снова
}

Распространённые ошибки

// 1. Вызов хуков условно
function Bad({ isLoggedIn }) {
  if (isLoggedIn) {
    // ОШИБКА!
    const [user, setUser] = useState(null);
  }
}

// 2. Вызов хуков в цикле
function Bad() {
  for (let i = 0; i < 10; i++) {
    // ОШИБКА!
    const [value, setValue] = useState(0);
  }
}

// 3. Вызов хуков в другой функции
function Bad() {
  const initializeState = () => {
    // ОШИБКА!
    const [value, setValue] = useState(0);
  };
}

Итоговое резюме

useState запоминает состояние через:

  1. Fiber узлы — каждый компонент имеет свой узел в памяти React'а
  2. Порядок вызовов — состояние привязано к позиции хука в функции
  3. Замыкания — setState знает где его состояние хранится
  4. Очередь обновлений — изменения запоминаются и применяются пакетами
  5. Batch updates — React оптимизирует несколько setState в один рендер

Поэтому критично:

  • Вызывать хуки в одном порядке
  • Не вызывать хуки условно или в циклах
  • Использовать функциональные обновления для асинхронного кода

Этот механизм позволяет React отслеживать состояние каждого компонента независимо и эффективно управлять перерендерами.

Как useState запоминает прошлое состояние? | PrepBro