Гид по технологиям

Локальный state в React: когда и почему

5 min read React Обновлено 01 Apr 2026
Локальный state в React: когда и почему
Локальный state в React: когда и почему

Женщина работает за компьютером Acer

Кратко: храните состояние там, где оно используется. Локальный state упрощает компоненты, уменьшает количество props и снижает когнитивную нагрузку. Когда несколько компонентов действительно разделяют данные — тогда поднимайте состояние вверх или используйте Context/Redux.

В этой статье объясняю, почему часто ошибочно хранят состояние в родительских компонентах, когда оно нужно только дочернему, и как рефакторить код, чтобы сделать состояние локальным. Приведены практические примеры, альтернативы, чек-листы для миграции и схема принятия решения.

Зачем делать состояние локальным

Коротко:

  • Локальное состояние уменьшает поверхность изменений: изменение состояния затрагивает только один компонент.
  • Компоненты становятся более автономными и переиспользуемыми.
  • Меньше передачи props и меньше «протаскивания» функций setX через дерево компонентов.

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

Базовый пример: счётчик

Ниже — исходный (типичный) пример, где состояние создаётся в App и прокидывается в Counter через props:

import {useState} from 'react'  
import {Counter} from 'counter'  
  
function App(){  
  const [count, setCount] = useState(0)  
  return   
}  
  
export default App  

Компонент Counter использует count и setCount:

function Counter({count, setCount}) {  
  return (  
    
{count}
) }

Проблема: в этом примере только Counter нуждается в count. Мы подняли состояние на уровень выше без необходимости — это увеличивает связность и усложняет рефакторинг.

Перемещаем состояние в дочерний компонент

Лучше объявить state прямо в Counter:

import {useState} from 'react'  
  
function Counter() {  
  const [count, setCount] = useState(0)  
  return (  
    
{count}
) }

Тогда App упрощается:

  // imports  
function App(){  
  return   
}  

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

Формы: когда state кажется естественным на родителе

Частая ситуация — форма: parent хранит объект formData и передаёт data + updateData в LoginForm. Пример:

import { useState } from "react";  
import { LoginForm } from "./LoginForm";  
  
function App() {  
  const [formData, setFormData] = useState({  
     email: "",  
     password: "",  
  });  
  
  function updateFormData(newData) {  
    setFormData((prev) => {  
      return { ...prev, ...newData };  
    });  
  }  
  
  function onSubmit() {  
    console.log(formData);  
  }  
  
  return (  
      
  );  
}  

LoginForm получает данные и обновляет их:

function LoginForm({ onSubmit, data, updateData }) {  
  function handleSubmit(e) {  
    e.preventDefault();  
    onSubmit();  
  }  
  
  return (  
    
updateData({ email: e.target.value })} /> updateData({ password: e.target.value })} />
); }

Если LoginForm — единственный потребитель этих полей, имеет смысл перенести всю логику в LoginForm. Один из вариантов — использовать useRef, чтобы уменьшить количество перерисовок:

import { useRef } from "react";  
  
function LoginForm({ onSubmit }) {  
  const emailRef = useRef();  
  const passwordRef = useRef();  
   
  function handleSubmit(e) {  
    e.preventDefault();  
    onSubmit({  
      email: emailRef.current.value,  
      password: passwordRef.current.value,  
    });  
  }  
  
  return (  
    
); }

В App остаётся только функция onSubmit, которую вызывает LoginForm с данными. Это упрощает код и снижает число обновлений состояния, если вам не нужно реагировать на каждое изменение ввода.

Важно: useRef хранит значения вне цикла рендера — это даёт профит по производительности, но теряется немедленная синхронизация UI с state. Используйте ref, когда вам действительно не нужна синхронная реакция на каждое изменение.

Когда локальный state не подходит

Контрпримеры:

  • Несколько разных компонентов в дереве требуют одних и тех же данных (например, список задач и компонент-индексатор количества задач). В этом случае поднимите состояние в ближайший общий предок.
  • Данные должны сохраняться между страницами или закладками — тогда имеет смысл использовать глобальное хранилище или sync с сервером/localStorage.
  • Сложная логика, где множество действий меняют один набор данных — удобнее централизовать через Context/Reducer/Redux для прогнозируемости.

Альтернативы и уровни зрелости управления состоянием

От простого к сложному:

  1. Локальный useState / useRef в компоненте. Лучше всего для изолированного поведения.
  2. Подъём состояния до ближайшего общего предка. Подходит для нескольких дочерних компонентов.
  3. Context + useReducer для средних приложений, где нужно пересылать данные через несколько уровней.
  4. Лёгкие сторонние сторы (Zustand, Jotai) для удобства и производительности.
  5. Redux / MobX для больших приложений с требованием предсказуемости, инструментов и DevTools.

Выбор должен основываться на сложности требований, не на предпочтениях команды.

Практическая методология миграции к локальному состоянию

Мини-методология (шаги):

  1. Найдите пропсы, которые используются только в одном компоненте.
  2. Перенесите useState/useRef в этот компонент.
  3. Удалите прокидывание setX и value из родителей.
  4. Запустите тесты/фикс ручной проверки поведения.
  5. Если родитель не использует state более нигде, удалите лишние поля и функции.

Рекомендация: делайте изменения маленькими коммитами, чтобы их можно было откатить.

Чек-лист для разработчика и ревьюера

Для разработчика:

  • Компонент использует state только если он его визуально/логически требует.
  • Нет лишнего протаскивания set-функций через несколько уровней.
  • Формы, которые не нуждаются в немедленной валидации, используют ref вместо state, если это улучшает производительность.

Для ревьюера:

  • Можно ли спрятать state глубже, не ломая поведение?
  • Есть ли дублирование кода при поднятом состоянии?
  • Не нарушается ли принцип единственной ответственности компонента?

Критерии приёмки

  • Поведение компонентов не изменилось для пользователя после рефактора.
  • Нет утечки props (props drilling) более чем на 1–2 уровня туда, где можно избежать.
  • Тесты покрывают критичные сценарии ввода и отправки форм.

Decision flowchart

flowchart TD
  A[Нужны ли данные в нескольких компонентах?] -->|Да| B[Найти ближайшего общего предка]
  B --> C{Пара компонентов или приложение-wide}
  C -->|Пара| D[Поднять state в общий предок]
  C -->|Application-wide| E[Рассмотреть Context/Redux/Zustand]
  A -->|Нет| F[Держать state локальным в компоненте]
  E --> G[Выбрать инструмент по сложности и требованиям]

Тесты и критерии приёмки для примеров

Тестовые сценарии для Counter:

  • Нажатие “+” увеличивает отображаемое число на 1.
  • Нажатие “-“ уменьшает число на 1.
  • Несколько экземпляров Counter на странице работают независимо.

Для LoginForm:

  • При вводе email/password и сабмите вызывается onSubmit с корректными полями.
  • При использовании useRef не происходит лишних ререндеров при каждом вводе (инструментально можно проверить через профайлер).

Меры предосторожности и подводные камни

  • Не переносите логику в локальное состояние, если родитель должен валидировать/агрегировать данные.
  • При использовании ref вы теряете реактивность на каждое изменение — это может повлиять на немедленную валидацию или отображение ошибок.
  • Локализация state не означает отказ от хорошей архитектуры: всё ещё полезно иметь одно место для сложной бизнес-логики.

Советы по производительности

  • Используйте useRef для неинтерактивных полей, которые не влияют на отображение во время ввода.
  • Мемоизируйте обработчики и вычисления (useCallback, useMemo) только при реальной необходимости.
  • Избегайте лишних подъемов состояния, которые приводят к массовым перерисовкам многих дочерних компонентов.

Короткий словарь

  • state: локальные данные компонента, которые влияют на рендер.
  • props drilling: множество уровней передачи props вниз по дереву.
  • ref: ссылка на DOM-элемент или мутабельный контейнер, не вызывающий ререндер при изменении.

Короткое резюме

  • Держите state как можно ближе к месту использования.
  • Поднимайте состояние только тогда, когда это действительно необходимо для нескольких компонентов.
  • Для форм используйте ref, если хотите уменьшить число ререндеров и не нуждаетесь в немедленной синхронизации UI.
  • Рассмотрите Context/Redux/современные сторы для широкого разделения состояния.

Полезно иметь правило в проекте: сначала пробовать локальный state, и только если есть явная потребность — рефакторить в сторону поднятия состояния или глобального хранилища.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Trello для фрилансера — управление проектами и клиентами
Productivity

Trello для фрилансера — управление проектами и клиентами

Идеальная фотосессия беременных: 6 ключевых советов
Фотография

Идеальная фотосессия беременных: 6 ключевых советов

Слои в фотографии: добавить глубину и выразительность
Фотография

Слои в фотографии: добавить глубину и выразительность

Как делать лучшие headshot-портреты
Фотография

Как делать лучшие headshot-портреты

Как снимать отличные фото на вечеринке
Фотография

Как снимать отличные фото на вечеринке

Как заблокировать отслеживание Facebook
Приватность

Как заблокировать отслеживание Facebook