Как правильно использовать useEffect и useState в React

Последнее обновление: 02/12/2026
  • Разберитесь, как useState сохраняет и обновляет локальное состояние компонента, включая функциональные обновления и обработку объектов.
  • Используйте useEffect для побочных эффектов с понятной логикой настройки/очистки и точными массивами зависимостей, чтобы избежать утечек памяти и зацикливаний.
  • Используйте useState и useEffect для решения реальных задач, таких как получение данных, подписки и обновление DOM, в функциональных компонентах.
  • Следуйте правилам использования хуков и рассматривайте эффекты как процессы, выполняемые «после рендеринга», чтобы компоненты React оставались предсказуемыми и удобными для сопровождения.

Хуки React useState и useEffect

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

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

Почему именно хуки, и почему именно useState и useEffect?

В React 16.8 были добавлены хуки, позволяющие функциональным компонентам использовать состояние и возможности жизненного цикла без классов.Раньше приходилось писать классовые компоненты для хранения локального состояния, подписки на внешние данные или реагирования на события жизненного цикла, такие как монтирование и размонтирование.

Главная проблема с классами заключалась в том, что связанная с ними логика часто распределялась между несколькими методами жизненного цикла. такие как componentDidMount, componentDidUpdate и componentWillUnmountВ итоге вы получите фрагменты одной и той же функции, разбросанные по разным методам в зависимости от... когда они бегут вместо почему Да, это так, что затрудняет чтение, тестирование и повторное использование кода.

Крючки позволяют перевернуть эту модель.: с useState вы напрямую прикрепляете состояние к функциональному компоненту, и с помощью useEffect Вы прикрепляете побочные эффекты непосредственно к логике, которая в них нуждается. Таким образом, вы можете сгруппировать все, что связано с одной задачей, в одном месте и легко извлекать многократно используемые хуки в дальнейшем.

Среди всех крючков, useState и useEffect являются основными примитивамиБольшинство повседневных функций можно реализовать, используя всего два инструмента: состояние пользовательского интерфейса, такое как формы и переключатели, сетевые запросы, подписки, таймеры, обновления DOM и многое другое. Другие хуки (useRef, useReducer, useContext, useMemo…) великолепны, но они строятся на одних и тех же идеях.

Правила использования хуков React, которые никогда нельзя нарушать

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

Первое правило: вызывайте хуки только внутри функциональных компонентов React или пользовательских хуков.Вы не можете использовать useState or useEffect в компонентах класса, обычных вспомогательных функциях или вне каких-либо компонентов. Подобный шаблон недопустим:

import React, { Component, useState } from 'react';

class App extends Component {
  // ❌ This will throw - hooks don’t work in classes
  const  = useState(0);
  render() {
    return <h1>Hello, I am a Class Component!</h1>;
  }
}

Правильный подход заключается в переходе к функциональному компоненту, если вы хотите использовать хуки.:

import React, { useState } from 'react';

function App() {
  const  = useState('');

  return (
    <div>
      Your JSX code goes in here...
    </div>
  );
}

export default App;

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

function BadComponent({ enabled }) {
  if (enabled) {
    // ❌ Wrong: hook inside a conditional
    const  = useState(0);
  }
  // ...
}

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

function ConditionalEffectComponent() {
  const  = useState(false);

  useEffect(() => {
    if (isMounted) {
      console.log('Component mounted');
    }
  }, );

  return (
    <div>
      <button onClick={() => setIsMounted(!isMounted)}>
        {isMounted ? 'Unmount' : 'Mount'}
      </button>
    </div>
  );
}

Третье подразумеваемое правило заключается в том, что хуки должны импортироваться из React (или из библиотеки хуков), а не реализовываться произвольно.Это очевидно, но стоит упомянуть: вся магия заключается во внутреннем диспетчере хуков React, который отслеживает вызовы хуков при каждом рендеринге.

Правильное управление локальным состоянием с помощью useState

useState позволяет привязать состояние к функциональному компоненту и получать как текущее значение, так и функцию обновления.Концептуально это функциональный аналог this.state и this.setState в компонентах класса.

Пример минимального счетчика с useState выглядит так:

import React, { useState } from 'react';

function Counter() {
  const  = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default Counter;

Когда вы звоните useState(initialValue)React сохраняет это состояние и возвращает пару.: текущее значение состояния и сеттер. В отличие от обычных локальных переменных, состояние сохраняется между рендерами, поэтому count Значение не сбрасывается до 0 каждый раз при выполнении функции компонента.

Для состояния можно использовать любое сериализуемое значение: числа, строки, логические значения, массивы, объекты и даже функции.. Вы также можете позвонить useState Для разделения связанных значений и извлечения всего в один объект, можно использовать несколько раз в одном компоненте.

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

setCount(prev => prev + 1);

Ещё одна тонкая, но важная деталь заключается в том, что вызов сеттера заменяет всё значение состояния целиком, а не объединяет объекты, как, например, this.setState в классахЕсли ваше состояние представляет собой объект или массив, вам необходимо самостоятельно распространить предыдущее значение:

const  = useState({ name: 'Alex', age: 30 });

// ✅ Correct: copy and update
setUser(prev => ({ ...prev, age: prev.age + 1 }));

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

const  = useState(() => calculateInitialValue());

Обработка побочных эффектов с помощью useEffect

useEffect Это API React для выполнения побочных эффектов в функциональных компонентах.«Побочным эффектом» считается всё, что взаимодействует с внешним миром: получение данных, ведение журналов, прямые изменения DOM, подписки, таймеры, API браузера и т. д.

Концептуально, useEffect заменяет комбинацию componentDidMount, componentDidUpdate и componentWillUnmount из компонентов классаВместо того чтобы распределять один эффект между тремя методами жизненного цикла, вы объявляете его один раз и позволяете React самостоятельно определять, когда он выполняется и когда происходит очистка ресурсов.

Основная подпись: useEffect(setup, dependencies?), setup Функция — это тело вашего эффекта; она может опционально возвращать функцию очистки. dependencies Массив указывает React, когда эффект необходимо запустить повторно.

useEffect(() => {
  // side effect logic here

  return () => {
    // optional cleanup logic here
  };
}, );

По умолчанию, без второго аргумента, эффект будет выполняться после каждого рендеринга. (первое монтирование и каждое последующее обновление). Зачастую это слишком большая нагрузка для сетевых запросов или ресурсоемкой логики.

Очень распространённый подход заключается в обновлении внешнего источника данных всякий раз, когда изменяется какой-либо элемент состояния.Например, обновление заголовка страницы в зависимости от количества кликов:

import React, { useState, useEffect } from 'react';

function Counter() {
  const  = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, ); // effect re-runs only when `count` changes

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

Массив зависимостей имеет решающее значение для производительности и корректности.Он определяет, когда React должен повторно запустить эффект: если какая-либо зависимость изменилась в соответствии с... Object.is После сравнения эффект устраняется, и процесс повторяется; если ничего не изменилось, он пропускается.

Понимание массива зависимостей на профессиональном уровне

В массиве зависимостей содержится самая тонкая деталь. useEffect ошибки происходят изReact сравнивает каждый элемент массива с его предыдущим значением, используя Object.isЕсли все значения равны, действие пропускается; если хотя бы одно значение отличается, действие выполняется повторно.

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

  • Второй аргумент отсутствует.Эффект запускается после каждого рендеринга.
  • Пустой массив []Эффект выполняется только один раз при монтировании и завершается при размонтировании.
  • Массив со значениями Эффект проявляется после монтирования и всякий раз, когда изменяется какая-либо зависимость.

Когда зависимости представляют собой примитивные значения (числа, строки, логические значения), это не представляет сложности.Проблемы начинаются, когда вы помещаете объекты, массивы или функции внутрь зависимостей, поскольку равенство основано на ссылках. Два идентичных объекта с разными ссылками считаются «различными», что приводит к повторному запуску при каждом рендеринге.

Рассмотрим эффект, зависящий от team объект из свойств:

function Team({ team }) {
  useEffect(() => {
    console.log(team.id, team.active);
  }, ); // ⚠️ might re-run every render if `team` reference changes
}

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

Более безопасная версия отслеживает только то, что действительно необходимо для достижения эффекта.:

function Team({ team }) {
  const { id, active } = team;

  useEffect(() => {
    console.log(id, active);
  }, );
}

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

function Team({ team }) {
  const { id, active, name } = team;

  useEffect(() => {
    const localTeam = { id, active, name };
    // use `localTeam` here
  }, );
}

В крайнем случае можно использовать мемоизацию. useMemo or useCallback для дорогостоящих предметов или функцийНо помните, что мемоизация сама по себе имеет свою цену. Не стоит добавлять её повсюду «на всякий случай»; добавляйте её только тогда, когда конкретная зависимость действительно вызывает проблемы с производительностью.

Правильная очистка эффектов

Некоторые побочные эффекты приводят к выделению ресурсов, которые необходимо высвободить.: подписки, сокеты, интервалы, тайм-ауты, обработчики событий и т. д. Забыв очистить их, легко можно столкнуться с утечками памяти или дублированием работы.

In useEffectОчистка выполняется путем возврата функции из эффекта.React вызовет эту функцию перед повторным запуском эффекта с новыми зависимостями, а также в последний раз при размонтировании компонента.

import { useEffect } from 'react';

function LogMessage({ message }) {
  useEffect(() => {
    const log = setInterval(() => {
      console.log(message);
    }, 1000);

    return () => {
      clearInterval(log);
    };
  }, );

  return <div>logging to console "{message}"</div>;
}

В этом примере — каждый раз message В результате изменений React сначала очищает старый интервал, а затем устанавливает новый с обновленным сообщением.Когда компонент исчезает из пользовательского интерфейса, последняя очистка окончательно сбрасывает интервал.

Эта пара «подготовка + уборка» является центральной в ментальной модели useEffectПостарайтесь рассматривать каждый эффект как самодостаточный процесс, который начинается в функции setup и полностью останавливается в функции cleanup. React может запускать несколько циклов setup/cleanup в режиме разработки (особенно в строгом режиме), чтобы проверить, действительно ли ваша очистка отменяет все изменения.

Классический пример — это подписка на внешний источник, например, API чата или событие браузера (см. Обработка события onKeyDown в React):

useEffect(() => {
  function handleClick(event) {
    console.log('Clicked', event.clientX, event.clientY);
  }

  document.addEventListener('click', handleClick);

  return () => {
    document.removeEventListener('click', handleClick);
  };
}, []); // runs once on mount, cleans up on unmount

Использование useState и useEffect вместе для получения данных

Одна из наиболее распространенных комбинаций в реальной жизни — это использование useState и useEffect для получения данных из APIВы храните данные (и, возможно, флаги загрузки/ошибок) в состоянии и выполняете запрос в эффекте, который запускается при монтировании компонента или при изменении какого-либо параметра.

Базовая схема получения данных после монтирования выглядит следующим образом.:

import { useEffect, useState } from 'react';

function FetchItems() {
  const  = useState([]);

  useEffect(() => {
    let ignore = false;

    async function fetchItems() {
      try {
        const response = await fetch('/items');
        const fetchedItems = await response.json();
        if (!ignore) {
          setItems(fetchedItems);
        }
      } catch (error) {
        console.error('Error fetching items:', error);
      }
    }

    fetchItems();

    return () => {
      // avoid updating state if the component unmounted
      ignore = true;
    };
  }, []);

  return (
    <div>
      {items.map(item => (
        <div key={item.id ?? item}>{item.name ?? item}</div>
      ))}
    </div>
  );
}

Здесь пустой массив зависимостей гарантирует, что запрос будет выполнен ровно один раз.. внутренний ignore Этот флаг — простой способ избежать установки состояния для несмонтированного компонента в случае задержки обработки запроса.

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

const Statistics = () => {
  const  = useState([]);
  const  = useState(true);

  useEffect(() => {
    const getStats = async () => {
      try {
        const statsData = await getData();
        setStats(statsData);
      } finally {
        setLoading(false);
      }
    };

    getStats();
  }, []);

  if (loading) {
    return <div>Loading statistics...</div>;
  }

  return (
    <ul>
      {stats.map(stat => (
        <li key={stat.id}>{stat.label}: {stat.value}</li>
      ))}
    </ul>
  );
};

Если ваш запрос зависит от параметра (например, категории, фильтра или параметра маршрута), добавьте этот параметр в массив зависимостей. Таким образом, эффект повторяется при изменении:

useEffect(() => {
  async function fetchItems() {
    const response = await fetch(`/items?category=${category}`);
    const data = await response.json();
    setItems(data);
  }

  fetchItems();
}, );

Размышляю в категориях «эффекты на каждом рендере» и «жизненные циклы».

Если вы привыкли к компонентам классов, может возникнуть соблазн мысленно составить карту. useEffect методы монтирования/обновления/размонтированияНо это обычно приводит к еще большей путанице. Более простая мысленная модель такова: «эффекты запускаются после рендеринга и могут завершиться перед следующим запуском».

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

Такая конструкция также устраняет целый класс ошибок, связанных с некорректной обработкой обновлений.Например, в компоненте класса, который подписывается на статус друга в сети, легко забыть повторно подписаться, когда props.friend Изменения, приводящие к устаревшим подпискам или сбоям при размонтировании. useEffect это перечисляет friend.id В качестве зависимости React автоматически выполнит очистку для старого «друга» и настройку для нового.

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

Оптимизация и устранение неполадок в работе функции useEffect.

Если какой-либо эффект срабатывает чаще, чем вы ожидаете, первое, что следует проверить, — это массив зависимостей.Либо зависимость меняется при каждом рендеринге (что часто встречается с встроенными объектами/функциями), либо вы вообще забыли указать массив.

Запись значений зависимостей в лог — это бысткий способ отладки.:

useEffect(() => {
  console.log('Effect deps:', dep1, dep2);
}, );

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

Бесконечные циклы возникают, когда эффект одновременно зависит от значения и безусловно обновляет это же значение.. Например:

useEffect(() => {
  setCount(count + 1); // ⚠️ will cause a loop if `count` is a dependency
}, );

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

Иногда возникает необходимость прочитать последнее значение какого-либо состояния или свойств внутри эффекта, не вызывая при этом повторного запуска.В таких сложных сценариях используются новые API, такие как «события эффектов» (через useEffectEvent (см. документацию React) или ссылки могут помочь, но в большинстве практических случаев безопаснее и проще оставаться верным зависимостям.

Собираем всё воедино, используя useState и useEffect В конечном итоге все сводится к нескольким основным привычкам.Следуйте этим принципам: сохраняйте состояние небольшим и сфокусированным, отдавайте предпочтение функциональным обновлениям при получении нового состояния из старого, структурируйте эффекты вокруг пар «настройка/очистка», будьте честны и явны при работе с массивами зависимостей и всегда соблюдайте правила хуков, чтобы React мог надежно отслеживать, что куда относится. Следуя этим принципам, ваши компоненты остаются предсказуемыми, побочные эффекты работают корректно, а кодовая база React становится намного проще для развития по мере роста вашего приложения.

Теме статьи:
Решено: Как установить собственные хуки реагирования с помощью
Похожие посты: