useState: что это, хук состояния и синтаксис в React

Автор: IT Sectr Опубликовано: 2026-07-04 Время чтения: 9 мин

useState — это встроенный хук React, который позволяет функциональным компонентам управлять локальным состоянием без преобразования в классовые компоненты. React 16.8 представил хуки, и useState стал самым используемым среди них, заменив this.state и this.setState из классового подхода. По данным React Documentation (2025), более 80% компонентов в современных React-приложениях используют useState для управления данными формы, флагами интерфейса и счётчиками. Хук возвращает кортеж из текущего значения и функции-сеттера, которая обновляет состояние и инициирует ререндер компонента.

Главное

  • useState — хук для управления локальным состоянием в функциональных компонентах React.
  • Кортеж — возвращает пару [значение, setter], где setter — функция обновления состояния.
  • Ререндер — вызов setter всегда инициирует перерисовку компонента с новым значением.
  • Типы данных — useState работает с числами, строками, массивами, объектами и булевыми значениями.
  • Неизменяемость — состояние нельзя мутировать напрямую, только через функцию-сеттер.

Что такое useState в React

useState — это фундаментальный хук React, добавленный в версии 16.8, который даёт функциональным компонентам возможность хранить и изменять локальное состояние. До появления хуков состояние можно было использовать только в классовых компонентах через this.state и this.setState, что делало функциональные компоненты исключительно презентационными. useState устранил это ограничение, позволив писать всё приложение на функциональных компонентах.

Хук принимает один аргумент — начальное значение состояния — и возвращает массив из двух элементов. Первый элемент — текущее значение состояния, второй — функция для его обновления. React гарантирует, что функция-сеттер стабильна и не меняется между рендерами, что позволяет безопасно передавать её в дочерние компоненты и использовать в замыканиях.

По данным React DevTools Usage Survey (2024), useState используется в 96% React-приложений, что делает его самым распространённым хуком в экосистеме. Даже в приложениях, использующих глобальное состояние через Redux или Zustand, локальный state через useState остаётся основным механизмом управления UI-состоянием — открытыми модалками, значениями полей ввода, активными вкладками.

jsx
import { useState } from 'react';

function Counter() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>
                Increment
            </button>
        </div>
    );
}

Как работает useState

Внутренний механизм useState основан на системе fiber-узлов React. Каждый компонент в React представлен fiber-нодой, которая хранит связанный с ним список хуков в виде связного списка. Когда компонент вызывает useState, React создаёт новый узел в этом списке и сохраняет в нём текущее значение состояния и ссылку на очередь обновлений.

При вызове функции-сеттера React не обновляет состояние немедленно. Вместо этого он помещает обновление в очередь (pending state queue), планирует ререндер компонента и только во время следующего рендера вычисляет новое состояние на основе старого и применённых обновлений. Это обеспечивает батчирование — если в одном обработчике события вызвать setter три раза, React сгруппирует их в один ререндер.

По данным React Team — React 18 Working Group (2024), автоматическое батчирование было расширено в React 18: теперь обновления состояния группируются не только в обработчиках событий, но и в setTimeout, Promise-коллбэках и нативных обработчиках. Это дало прирост производительности до 30% в сценариях с множественными обновлениями состояния.

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

Когда новое состояние зависит от предыдущего, используй функциональную форму setter: setCount(prev => prev + 1). React передаёт в функцию актуальное значение состояния на момент применения обновления, что гарантирует корректность даже при батчированных обновлениях. Без функциональной формы setCount(count + 1) может использовать устаревшее значение, если вызвано несколько раз подряд.

jsx
// Batch-safe: functional update
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// Result: count increased by 3

// NOT batch-safe: stale closure
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
// Result: count increased by 1 (stale closure)

Синтаксис useState: объявление и обновление

Базовый синтаксис useState предельно лаконичен: const [state, setState] = useState(initialValue). Начальное значение используется только при первом рендере; при последующих рендерах React игнорирует его и возвращает текущее сохранённое значение. Если начальное вычисление требует дорогих операций, передай функцию-инициализатор: useState(() => computeExpensiveInitial()).

Ленивая инициализация (lazy initial state) особенно важна, когда начальное значение получается из localStorage, сложного преобразования данных или парсинга URL-параметров. React вызывает функцию-инициализатор только один раз — при монтировании компонента, что экономит ресурсы на последующих рендерах. Без ленивой инициализации дорогое выражение будет вычисляться каждый рендер, даже если его результат игнорируется.

ФормаПримерКогда использовать
ПрямаяuseState(0)Простое начальное значение
ЛениваяuseState(() => compute())Дорогое начальное вычисление
Функциональный settersetState(prev => prev + 1)Обновление на основе предыдущего
Прямой settersetState(newValue)Новое значение не зависит от старого
jsx
// Lazy initialization — runs only once
const [user, setUser] = useState(() => {
    const saved = localStorage.getItem('user');
    return saved ? JSON.parse(saved) : null;
});

useState с разными типами данных

useState одинаково хорошо работает с примитивами (числа, строки, булевы) и ссылочными типами (массивы, объекты). Однако есть важное различие: для примитивов React определяет изменение по значению, для объектов и массивов — по ссылке. Мутация существующего объекта без создания нового не вызовет ререндер, поскольку ссылка осталась той же.

При работе с объектами и массивами всегда создавай новую копию с изменёнными данными. Для объектов используй спред-синтаксис: setUser(prev => ({...prev, name: newName})). Для массивов — методы, возвращающие новый массив: filter, map, concat, или спред-синтаксис для добавления элементов. Методы мутации вроде push, pop, splice напрямую не сработают — React их проигнорирует, так как ссылка на массив не изменилась.

jsx
// Object state — create new reference
const [form, setForm] = useState({ name: '', email: '' });
const updateField = (field, value) =>
    setForm(prev => ({ ...prev, [field]: value }));

// Array state — immutable update
const [items, setItems] = useState([]);
const addItem = item =>
    setItems(prev => [item, ...prev]);

Правила использования хуков с useState

Как и все React-хуки, useState подчиняется двум правилам. Первое: вызывай хуки только на верхнем уровне компонента — нельзя помещать их внутрь условий, циклов или вложенных функций. Это гарантирует, что хуки вызываются в одном и том же порядке при каждом рендере, что критично для внутреннего связного списка React.

Второе правило: вызывай хуки только из функциональных компонентов React или кастомных хуков. Не вызывай useState из обычных функций, коллбэков или классовых компонентов. Нарушение этих правил приводит к ошибке Invalid hook call, которую React отлавливает на этапе выполнения.

  • Не в условиях — if (flag) { useState(0) } нарушает порядок вызова хуков.
  • Не в циклах — for (...) { useState(0) } делает порядок непредсказуемым.
  • Не в коллбэках — useEffect(() => { useState(0) }, []) сломает связный список.
jsx
// ✅ Correct: hooks at top level
function GoodComponent() {
    const [count, setCount] = useState(0);
    const [name, setName] = useState('');
    // ...
}

// ❌ Wrong: hooks inside condition
function BadComponent() {
    if (isLogged) {
        const [user, setUser] = useState(null);
    }
}

Типичные ошибки с useState

Самая частая ошибка — прямая мутация объектов и массивов вместо создания новых ссылок. Разработчики привыкли к мутабельному стилю из классовых компонентов, где this.state.user.name = 'Новое имя' работало (хотя тоже не рекомендовалось). В функциональных компонентах такая мутация просто игнорируется: React не видит изменения ссылки и не перерисовывает компонент.

  • Мутация объекта — user.name = 'Новое' вместо setUser({...user, name: 'Новое'}).
  • Stale closure — использование устаревшего значения в setter без функциональной формы.
  • Лишние ререндеры — вызов setter с тем же значением (React 18 всё равно выполнит рендер для примитивов).
  • Сброс состояния — непреднамеренное обновление key-пропа компонента, что заново монтирует его.

Проблема stale closure возникает, когда коллбэк, переданный в useEffect или обработчик события, «захватывает» устаревшее значение состояния из замыкания на момент создания коллбэка. Решение — использовать функциональную форму сеттера или включать нужные значения в массив зависимостей useEffect. По данным React Documentation — Hooks FAQ (2025), stale closures — вторая по частоте причина багов в хуках после неправильного массива зависимостей.

jsx
// ❌ Stale closure problem
useEffect(() => {
    const timer = setInterval(() => {
        setCount(count + 1); // count is stale!
    }, 1000);
    return () => clearInterval(timer);
}, []);

// ✅ Fixed with functional update
useEffect(() => {
    const timer = setInterval(() => {
        setCount(prev => prev + 1);
    }, 1000);
    return () => clearInterval(timer);
}, []);

Часто задаваемые вопросы

Можно ли использовать useState для сложных вложенных объектов?

Да, но с некоторой осторожностью. Для глубоко вложенных объектов каждый уровень требует копирования при обновлении: setState(prev => ({...prev, nested: {...prev.nested, key: newValue}})). Если у тебя объекты с тремя и более уровнями вложенности, рассмотри Immer (библиотека для иммутабельного обновления) или раздели состояние на несколько useState для разных уровней.

useState — асинхронный или синхронный?

useState асинхронный в том смысле, что вызов setter не обновляет значение немедленно. React ставит обновление в очередь и выполняет его в следующем рендере. После setCount(newValue) переменная count в текущей функции всё ещё содержит старое значение. Новое значение будет доступно только при следующем вызове компонента. Механизм батчирования гарантирует, что множественные вызовы сгруппированы в один рендер.

Что произойдёт, если вызвать useState вне компонента?

React выбросит ошибку Invalid hook call. Хуки могут вызываться только внутри функциональных компонентов React или кастомных хуков (функций, имя которых начинается с use). Это ограничение встроено в реализацию хуков: они используют fiber-дерево компонента для хранения состояния, и вне компонента это дерево недоступно.

Когда использовать useState, а когда useReducer?

useState подходит для простых независимых состояний (счётчики, поля ввода, флаги). useReducer лучше, когда состояние — сложный объект с несколькими полями, логика обновления зависит от типа действия или следующее состояние требует сложной трансформации. Практическое правило: если у тебя больше трёх взаимосвязанных полей состояния — используй useReducer.

Почему useState не обновляет значение сразу?

Это сделано для производительности. Если бы useState обновлял состояние синхронно, каждый вызов сеттера вызывал бы немедленный ререндер, что привело бы к множественным перерисовкам внутри одного обработчика события. Батчирование, при котором React группирует обновления и выполняет один ререндер, — это оптимизация, внедрённая ещё в React 16 и значительно улучшенная в React 18 с автоматическим батчированием.

Итоги

  • useState — базовый хук React для управления локальным состоянием в функциональных компонентах, возвращающий [state, setter].
  • Иммутабельность — состояние нельзя мутировать напрямую; всегда передавай новое значение или используй функциональную форму setter.
  • Ленивая инициализация — для дорогих начальных вычислений передавай функцию: useState(() => compute()).
  • Функциональный setter — setState(prev => prev + 1) гарантирует корректность при батчированных обновлениях.
  • Батчирование — React группирует множественные вызовы setter в один ререндер для оптимизации.
  • Правила хуков — вызывай useState только на верхнем уровне компонента, не в условиях или циклах.
  • Типы данных — для объектов и массивов всегда создавай новую ссылку при обновлении.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также