<StrictMode> это компонент React, который включает дополнительные проверки для всего внутри себя, только в разработке. Он рендерит каждый компонент дважды, при монтировании выполняет setup, cleanup и снова setup каждого эффекта, дважды вызывает ref-колбэки и предупреждает об устаревших API. Именно поэтому useEffect в разработке «выполняется дважды». В продакшен-сборке он ничего не делает.
Его включают, оборачивая приложение, обычно в main.jsx:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
);
Превью на этой странице работают как продакшен-сборка, поэтому видимого эффекта от StrictMode в них не было бы. Вместо этого в каждом примере ниже есть ошибка того рода, ради которой существует StrictMode, и вы можете вызвать её вручную. Здесь эффект добавляет обработчик клика на document и никогда его не удаляет. Скройте и покажите панель пару раз, затем кликните по странице.
Каждый Show добавляет ещё один обработчик, и ни один не удаляется, поэтому один клик выводит по строке на каждое монтирование. Это утечка, которую пользователь может вызвать, открывая и закрывая панель. StrictMode показал бы её при самом первом монтировании: в разработке он монтирует, размонтирует и монтирует снова, поэтому вы увидели бы по две строки на клик ещё до того, как коснулись кнопки.
Эффекты: setup, cleanup, setup
При монтировании StrictMode выполняет ваш эффект, затем его очистку, затем эффект снова. Он имитирует то, что происходит, когда пользователь уходит с экрана и возвращается, и проверяет, что очистка отменяет setup. Если отменяет, второй setup оставляет всё ровно так, как оставил бы один, и вы не видите ничего неправильного. Если нет, ошибка проявляется сразу.
Решение для логгера: очистка, которая удаляет ту же функцию, что была добавлена:
Переключайте сколько угодно: пока логгер показан, каждый клик выводит одну строку. Под StrictMode в разработке консоль начиналась бы с setup, cleanup, setup, и приложение по-прежнему вело бы себя правильно. Эта последовательность признак здорового эффекта, а не проблема, которую нужно отключать.
Частые эффекты, которым нужна очистка: обработчики событий, setInterval и setTimeout, подписки, соединения WebSocket или чата и запросы, ответ на которые нужно игнорировать, если компонента уже нет. Страница о useEffect показывает шаблон очистки для каждого.
Рендеры: вызываются дважды
StrictMode также вызывает функцию вашего компонента дважды на каждый рендер (а также функции, которые вы передаёте в useState, useMemo и useReducer, и constructor, render и shouldComponentUpdate классов). React сохраняет результат одного вызова, а другой выбрасывает. Чистый компонент оба раза возвращает один и тот же JSX, поэтому ничего не меняется.
Компонент, который во время рендера меняет что-то вне себя, не чистый, и двойной вызов заставляет изменение произойти дважды. Этот компонент добавляет запись «rendered at» в массив, который получает как пропс:
Каждый повторный рендер добавляет запись, о которой никто не просил. Здесь это заметно только после клика. Под StrictMode уже первый рендер показал бы две новые записи вместо одной. Решение: вычислять во время рендера без мутации: const shown = [...entries, 'extra'].
React 19 добавляет одну деталь: во время двойного рендера useMemo и useCallback переиспользуют результат первого вызова, а не вычисляют его дважды.
Ref-колбэки
Начиная с React 19 StrictMode также дважды выполняет ref-колбэки при монтировании: присоединение, очистка, присоединение. Ref-колбэк, который добавляет обработчик или где-то регистрирует узел, должен возвращать очистку, точно как эффект:
<div
ref={(node) => {
const observer = new ResizeObserver(onResize);
observer.observe(node);
return () => observer.disconnect();
}}
/>
Предупреждения об устаревших API
StrictMode предупреждает об API, которые уходят, в основном в классовых компонентах, например о старых методах жизненного цикла UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps и UNSAFE_componentWillUpdate. Некоторые API, о которых он раньше предупреждал, например строковые рефы и findDOMNode, полностью удалены из React 19.
Включение для части приложения
<StrictMode> это обычный компонент, поэтому можно обернуть только часть дерева. Это помогает при внедрении в большом приложении: начните с новых экранов и расширяйте.
function App() {
return (
<>
<Header />
<StrictMode>
<Sidebar />
<Content />
</StrictMode>
<Footer />
</>
);
}
Sidebar и Content (и всё, что они рендерят) получают проверки; Header и Footer нет. Отключить его для поддерева внутри строгой части нельзя.
Где он включён по умолчанию:
- Vite: шаблон React оборачивает
<App />в<StrictMode>вmain.jsx. - Next.js: App Router включает его по умолчанию; им управляет
reactStrictModeвnext.config.js.
В продакшене он ничего не делает
Продакшен-сборка игнорирует <StrictMode>: один рендер, один запуск эффекта, никаких предупреждений. Поэтому двойные вызовы ничего не стоят вашим пользователям, и убирать обёртку перед деплоем незачем.
Если двойной эффект что-то ломает в разработке, например запрос отправляется дважды или аналитическое событие логируется дважды, это полезная информация. Чаще всего решение это очистка (флаг ignore для запроса, отключение для соединения). Для того, что действительно должно происходить один раз за загрузку приложения, например инициализации SDK, выполняйте это вне любого компонента на уровне модуля или защищайте флагом на уровне модуля.
let didInit = false;
function App() {
useEffect(() => {
if (didInit) return;
didInit = true;
initAnalytics(); // runs once per page load, even in StrictMode
}, []);
}
Избегайте распространённого обходного пути с ref, который пропускает второй запуск эффекта. Он прячет предупреждение, не исправляя пропущенную очистку, и ошибка возвращается в первый же раз, когда компонент действительно размонтируется и монтируется снова, как это делает логгер выше.
Часто задаваемые вопросы
Что делает StrictMode в React?
Только в разработке он рендерит каждый компонент внутри себя дважды, при монтировании компонента выполняет setup, cleanup и снова setup каждого эффекта, так же дважды вызывает ref-колбэки и предупреждает об устаревших API. Дополнительные вызовы выявляют нечистые рендеры и эффекты без очистки.
Почему мой useEffect выполняется дважды?
Потому что компонент находится внутри <StrictMode>, а вы запускаете сборку для разработки. React монтирует его, имитирует размонтирование (выполняется ваша очистка) и монтирует снова. Если второй setup вызывает ошибку, эффекту не хватает очистки.
Влияет ли StrictMode на продакшен?
Нет. Продакшен-сборка рендерит один раз и выполняет каждый эффект один раз, с <StrictMode> или без него. Его можно оставить в коде.
Как отключить StrictMode?
Уберите обёртку <StrictMode>, обычно в main.jsx или index.js, или установите reactStrictMode: false в next.config.js. Обычно лучше исправить очистку: двойной запуск указывает на ошибку, на которую могут наткнуться реальные пользователи.
Почему console.log выводится дважды?
В разработке StrictMode вызывает функцию компонента дважды за рендер. React DevTools в своих настройках может делать логи второго вызова серыми или скрывать их.