useState в React: руководство по функциональным компонентам

Быстрые ссылки
Классический подход
Преобразование в функциональный компонент
Устройство хука useState
Обновление состояния
Начальные значения
Использование нескольких значений состояния
Когда useState не подходит
Альтернативы и рекомендации
Руководство по миграции из классов в функции
Заключение
Классический подход
В классических React-компонентах состояние хранится в свойстве state экземпляра компонента. Для изменения состояния используется метод setState(), который частично объединяет (shallow merge) новое значение с текущим состоянием и вызывает перерендер.
Пример классового компонента с простым счётчиком:
class MyComponent extends React.Component {
state = { value: 1 };
updateState = () => this.setState({ value: (this.state.value + 1) });
render() {
return (
{this.state.value}
);
}
}В этом примере текст всегда отображает текущее число из состояния. Нажатие на кнопку увеличивает значение.
Важно: setState выполняет поверхностное слияние объекта, поэтому можно обновлять часть объекта состояния, не создавая полностью новый объект вручную.
Преобразование в функциональный компонент
Функциональные компоненты по умолчанию были «без состояния» до появления хуков. Начиная с React 16.8 появилась возможность использовать состояние в функциях с помощью хука useState(). Это делает компоненты короче и читабельнее.
Эквивалент предыдущего компонента с useState:
import React, { useState } from "react";
const MyComponent = () => {
const [value, setValue] = useState(1);
return (
{value}
);
};Отличия и преимущества:
- Меньше шаблона (нет class/constructor).
- Явные имена переменных и функций-обновителей (по соглашению setX).
- Легче комбинировать с другими хуками (useEffect, useReducer и т. д.).
Устройство хука useState
useState — это функция, которая создаёт «ведро» состояния для конкретного состояния в рамках вызова вашей функции-компонента. Она возвращает массив из двух элементов: текущее значение и функцию-обновитель.
Ключевые моменты:
- useState гарантирует сохранность значения между рендерами (React хранит его за пределами вашей функции).
- Вызовы useState должны быть детерминированы и находиться в одном и том же порядке при каждом рендере (правило хуков).
- Обновитель заменяет предыдущее значение новым (в отличие от setState в классах, который выполняет поверхностное слияние объектов).
Пример с именованием:
const [value, setValue] = useState(1);По соглашению функция называется setX, где X — имя переменной состояния.
1-строчное определение: useState создаёт локальную ссылку на значение, которая сохраняется между вызовами функции-компонента.
Обновление состояния
Функция-обновитель, возвращаемая useState, ведёт себя как обычная функция: вы вызываете её с новым значением или передаёте функцию, которая получит текущее состояние и вернёт следующее.
Примеры:
Прямое присвоение:
setValue(5);Функциональный обновитель (рекомендуется при вычислениях от предыдущего состояния):
setValue(prev => prev + 1);Почему это важно:
- Если обновление зависит от предыдущего значения, нужно использовать функциональный паттерн, чтобы избежать ошибок со «сталыми» замыканиями и конкурентными обновлениями.
- React может батчировать обновления и вызывать несколько обновителей подряд; функциональный стиль гарантирует последовательность вычислений.
Отличие от setState в классах:
const [value, setValue] = useState({ foo: "bar", test: { example: "demo" } });
setValue({ foo: "foobar" });
// Результат: { foo: "foobar" }
// В классе:
this.state = { foo: "bar", test: { example: "demo" } };
this.setState({ foo: "foobar" });
// Результат: { foo: "foobar", test: { example: "demo" } }useState заменяет значение полностью. Если вам нужно обновить часть объекта, создайте новый объект с помощью spreаd-оператора или используйте useReducer.
Пример обновления поля объекта:
setUser(prevUser => ({ ...prevUser, username: "newName" }));Проблемы и типичные ошибки:
- Мутирование объекта состояния вместо создания нового объекта приведёт к тому, что React не заметит изменения.
- Использование устаревших значений в обработчиках при отсутствии функционального обновителя.
- Передача функции-обновителя вместо результата: setValue(toggle) — допустимо, если toggle — функция, принимающая предыдущее значение.
Начальные значения
useState принимает начальное значение в качестве аргумента. Это может быть примитив, объект или функция-инициализатор.
Прямое значение:
const [value, setValue] = useState(1);Ленивая инициализация (функция вызывается только при монтировании):
const initialState = () => computeExpensiveValue();
const [value, setValue] = useState(initialState);Если вы передаёте функцию-инициализатор (например, doSomethingExpensive), React вызовет её один раз при первой инициализации состояния. Это экономит ресурсы, потому что вычисление не выполняется на каждом рендере.
Важное примечание про режим Strict Mode в разработке:
- В React Strict Mode (разработка) некоторые функции инициализации могут быть вызваны дважды, чтобы обнаружить побочные эффекты. Это влияет на функции-инициализаторы: они могут быть вызваны повторно в процессе проверки, поэтому начинать побочные эффекты из функции-инициализатора не рекомендуется.
Использование нескольких значений состояния
Есть два основных подхода:
- Одно состояние-объект (как в классах)
const [user, setUser] = useState({ id: 1, username: "foobar" });
setUser({ ...user, username: "example" });Плюсы:
- Похож на поведение классов.
Минусы:
- Вам нужно вручную сливать свойства.
- Обновления могут быть менее локализованы и сложнее тестировать.
- Несколько useState для отдельных частичных состояний (рекомендуется)
const [userId, setUserId] = useState(1);
const [username, setUsername] = useState("foobar");Плюсы:
- Явные «ведра» состояния для каждой логической единицы.
- Проще оптимизировать локальные обновления и мемоизацию.
Минусы:
- Больше строк кода, если значений много.
Выбор зависит от семантики: если поля тесно связаны и всегда обновляются вместе, храните их в одном объекте; если поля независимы — используйте отдельные useState.
Когда useState не подходит
useState хорош для локального примитивного состояния, флагов, форм и отдельных полей. Но есть случаи, когда лучше другие инструменты:
- Сложная логика обновления с множественными ветвлениями и побочными эффектами — рассмотрите useReducer.
- Глобальное состояние, используемое многими компонентами — рассмотрите Context + useReducer, или внешние решения (Redux, Zustand, Recoil).
- Частые изменения большого глубоко вложенного объекта — useState с глубокими копиями приводит к ошибкам и низкой производительности; подумайте о нормализации состояния или useReducer.
Альтернативы и сочетания
useReducer: полезен, когда у вас есть сложное управление состоянием с бизнес-логикой и множеством типов действий. Reducer позволяет централизовать переходы состояний и облегчает тестирование.
Context: предоставляет доступ к состоянию от верхнего уровня приложения без проброса пропсов; часто комбинируется с useReducer для управления глобальным состоянием.
Лёгкие сторонние решения (Zustand, Jotai, Recoil): подходят, если нужен минималистичный глобальный стор.
Комбинации:
- Используйте useState для локального UI-состояния (открыт/закрыт модал) и useReducer/Context для доменной логики или данных, которые нужны в нескольких местах.
Ментальные модели и эвристики
- «Ведро на каждую причину изменения»: держите отдельное состояние для независимых частей интерфейса.
- «Компоновка вместо единого объекта»: если части состояния естественно отделимы, разделите их — это упрощает логику.
- «Функция-обновитель против прямого значения»: всегда используйте функциональный обновитель, если новое значение вычисляется из старого.
Короткая шпаргалка:
- Флаги, счётчики, ввод — useState.
- Множественные действия и сложные переходы — useReducer.
- Данные приложения, которыми делятся многие компоненты — Context/Store.
Руководство по миграции из классов в функциональные компоненты
Ниже — пошаговый план для разработчиков, которые переводят компонент на хуки.
Шаги миграции:
- Замените class на const MyComponent = () => {} и перенесите JSX из render.
- Переместите начальное state в useState: const [state, setState] = useState(initialState).
- Перепишите методы класса как функции внутри компонента; замените this на локальные переменные/замыкания.
- Замените this.setState на setState; при частичном обновлении объекта используйте spreаd или разбейте состояние на несколько useState.
- Перенесите побочные эффекты (lifecycle) в useEffect с корректными зависимостями.
- Проверьте обработчики и привязку контекста — этого теперь не требуется.
- Покройте компонент тестами: проверяйте поведение при кликах, вводе, инициализации состояния.
Критерии приёмки:
- Вся функциональность сохраняется (UI и поведение).
- Нет подвешанных ссылок на this.
- Тесты, которые проходили ранее, проходят и для нового компонента.
- Нагрузка и производительность сравнимы с исходным компонентом.
Пример playbook для команды:
- Подготовка: написать unit-тесты или E2E для текущего поведения.
- Миграция: перенос кода и замена lifecycle на useEffect.
- Рефакторинг: выделение логики в кастомные хуки при необходимости.
- Проверка: просмотреть профили CPU/рамки памяти при сложных компонентов.
Тест-кейсы и приёмка
Общие тесты при переходе на useState:
- Инициализация: компонент показывает исходное значение состояния.
- Обновление: клик/ввод меняет состояние и UI отражает это.
- Параллельные обновления: несколько быстрых кликов корректно увеличивают счётчик.
- Ленивая инициализация: тяжёлая функция инициализируется один раз при монтировании (в продакшне).
Примеры тестов (Jest + React Testing Library):
Проверка клика инкремента:
- Рендер компонента — убедиться, что число 1 отображается.
- Вызывать клик на кнопке — ожидать 2.
Проверка функционального обновителя, при множественных вызовах:
- Вызывать setValue(prev => prev + 1) несколько раз — итог совпадает с суммой.
Edge-case gallery (частые грабли)
- Мутация объекта состояния: изменение свойства объекта без создания нового объекта — React может не обнаружить изменение.
- Неправильный порядок хуков: условный вызов useState приведёт к ошибке «Hooks must be called in the same order».
- Замыкания: обработчики, использующие устаревшие значения состояния, если не применялся функциональный обновитель.
- Дублирование инициализаторов в Strict Mode: не рассчитывайте на побочные эффекты внутри функции-инициализатора.
Производительность и оптимизация
- Разделяйте состояние на логические куски, чтобы предотвратить лишние перерендеры дочерних компонентов.
- Используйте React.memo, useMemo и useCallback для мемоизации значимых вычислений и колбеков.
- Если у вас сложная логика состояния с множеством действий, useReducer упрощает профилирование и оптимизации.
Безопасность и приватность
- useState сам по себе не влияет на безопасность, но будьте осторожны при хранении чувствительных данных (токенов, персональных данных) в состоянии браузера.
- Для длительного хранения и обмена между сессиями используйте безопасные механизмы (например, HTTP-only куки для токенов), а не localStorage, если необходимо соблюдать рекомендации по безопасности.
- Для соответствия требованиям GDPR убедитесь, что личные данные обрабатываются и сохраняются в соответствии с политиками компании и законодательства.
Совместимость и заметки по версиям
- Хуки доступны, начиная с React 16.8.
- Для новых возможностей (параллельный режим, автоматическое батчирование, Strict Mode поведение) следите за изменениями в релиз-нотах React (React 18 и выше ввели поведение автоматического батчинга для промисов/таймеров).
- При поддержке старых приложений, которые используют классы, комбинирование компонентов классов и функций в одном приложении допустимо.
Decision flow: выбрать useState или useReducer
flowchart TD
A[Нужна ли локальная простая логика?] -->|Да| B[useState]
A -->|Нет| C[Сложная логика / много действий]
C --> D[useReducer]
B --> E[Много независимых состояний?]
E -->|Да| F[Несколько useState]
E -->|Нет| G[Один useState с объектом]Чек-лист для разработчика, тимлида и QA
Разработчик:
- Замена this и методов на локальные функции.
- Использование functional updater при зависимостях от предыдущего состояния.
- Предотвращение мутаций. Создание новых объектов при обновлении.
Тимлид:
- Архитектура состояния согласована (локальное vs глобальное).
- План миграции и rollback есть.
QA:
- Тесты cover случаев инициализации и обновления.
- Проверка в режиме Strict Mode и в продакшн сборке.
Примеры полезных сниппетов
Тоггл-логика, повторно используемая:
const useToggle = (initial = false) => {
const [state, setState] = useState(initial);
const toggle = useCallback(() => setState(prev => !prev), []);
return [state, toggle];
};Ленивая инициализация с защитой от побочных эффектов:
const computeExpensiveValue = () => {
// тяжёлая синхронная операция
return heavyComputation();
};
const [value, setValue] = useState(() => computeExpensiveValue());Короткое объявление для команды (100–200 слов)
useState — базовый хук для управления локальным состоянием в функциональных компонентах React. Он возвращает текущее значение и функцию для его обновления; обновитель заменяет значение полностью, поэтому для частичных обновлений объектов используйте spreаd или функциональный обновитель. Для сложной логики лучше применять useReducer или сочетание Context + useReducer. При миграции классов на функции переносите state в useState, lifecycle-методы в useEffect и проверяйте поведение в режиме Strict Mode и через тесты. Используйте ленивую инициализацию для дорогих вычислений и функциональные обновители, чтобы избежать ошибок со старыми замыканиями.
Заключение
useState делает функциональные компоненты полными участниками парадигмы React, сохраняя простоту и читаемость. Выбирайте подход (несколько useState vs объект vs useReducer) согласно семантике данных и сложности логики. Помните про иммутабельность, функциональные обновители и ленивую инициализацию — это основные приёмы для надёжных и производительных React-компонентов.
Ключевые моменты:
- useState подходит для локального состояния и простых сценариев.
- Для сложной логики используйте useReducer.
- Всегда избегайте мутаций и используйте функциональные обновители при зависимости от предыдущего значения.
- Ленивую инициализацию используйте для тяжёлых вычислений.
Похожие материалы
Unity Lights: циферблат Apple Watch для Black History Month
Обновление видеодрайвера для Rainbow Six Siege
Ограничение частоты запросов в ASP.NET Core
Исправление лагов Android: TRIM и LagFix
Семафоры в Bash: что это и как реализовать