Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «React.StrictMode». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**React.StrictMode** - це обгортка тільки для розробки, яка двічі викликає рендери та ефекти, щоб виявити побічні ефекти та небезпечні патерни. ```jsx createRoot(document.getElementById('root')).render( <React.StrictMode><App /></React.StrictMode> ); ``` **Головне:** не впливає на продакшн; подвійні логи та подвійний запуск ефектів у dev - це навмисна поведінка, не баг.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**React.StrictMode** - це обгортка тільки для розробки, яка двічі викликає рендери та ефекти, щоб виявити побічні ефекти та небезпечні патерни до того, як вони потраплять у продакшн. ## Теорія ### TL;DR - Уяви авіасимулятор: StrictMode навантажує компоненти двічі під час розробки, щоб знайти слабкі місця до реальних користувачів - Рендерить компоненти двічі та запускає ефекти за схемою setup-cleanup-setup тільки в dev; продакшн-білди StrictMode повністю прибирає через tree-shaking - React 18+ додав подвійний монтаж ефектів, щоб знайти баги в cleanup, які проявляються в конкурентних фічах - Новий проект: огорни `<App />` одразу. Легасі-код: додавай StrictMode по одному піддереву. ### Швидкий приклад ```jsx // index.js import React, { useState, useEffect } from 'react'; import { createRoot } from 'react-dom/client'; function Counter() { const [count, setCount] = useState(0); console.log('Render'); // Виводиться двічі в dev useEffect(() => { console.log('Effect ran'); // Виводиться двічі при монтажі в dev }); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; } createRoot(document.getElementById('root')).render( <React.StrictMode> <Counter /> </React.StrictMode> ); // Dev: "Render" x2, "Effect ran" x2 // Продакшн: "Render" x1, "Effect ran" x1 ``` Подвійні логи - це не баг. Це саме те, для чого StrictMode існує. ### Що перевіряє StrictMode StrictMode виявляє чотири категорії проблем: - Нечисті функції рендеру: будь-яка мутація стану або зовнішніх значень під час рендеру спрацьовує двічі, що робить мутацію очевидною - Застарілі методи життєвого циклу: `componentWillMount`, `componentWillReceiveProps`, `componentWillUpdate` - всі видають попередження в консолі - Застарілі рядкові refs: стиль `ref="button"`, deprecated з React 16 і прибраний у React 19 - Неідемпотентні ефекти (React 18+): setup і cleanup ефекту запускаються в послідовності (setup, cleanup, setup), щоб перевірити, чи cleanup правильно скасовує те, що зробив setup ### Як працює подвійний виклик Fiber reconciler React огортає цільові компоненти в спеціальний dev-шлях всередині `ReactFiberStrictMode.js`. В режимі розробки він двічі викликає `render`, ініціалізатори `useState`, колбеки `useMemo` та setup/cleanup `useEffect`. DOM-мутації другого виклику відкидаються до того, як досягнуть браузера. Fiber-дерево спільне для обох проходів; React повторює виклики функцій і викидає результат другого проходу. Для користувача нічого видимого не змінюється. Для `useEffect` у React 18+ послідовність при монтажі така: setup запускається, cleanup запускається, setup запускається знову. Це симулює поведінку конкурентних фіч, як-от Transitions, де React може демонтувати і перемонтувати компонент. Якщо cleanup не скасовує те, що зробив setup, третій виклик ламається під час розробки, а не в продакшні. ### Ключова різниця від продакшну StrictMode не змінює того, що рендерять компоненти, і як додаток поводиться для користувачів. Він змінює тільки те, скільки разів React внутрішньо викликає функції під час розробки. Продакшн-білди прибирають обгортку StrictMode повністю, тому подвійні виклики не відбуваються поза dev. ### Коли використовувати - Новий проект на React 18+: огорни `<App />` одразу. Vite і Create React App роблять це за замовчуванням. - Міграція легасі-коду: додавай StrictMode до одного піддерева за раз, виправляй попередження, розширюй покриття. - Стороння бібліотека, яка спамить попередженнями, які ти не можеш виправити: залиш цей компонент за межами обгортки і повідом авторів бібліотеки. ```jsx // Вибіркова обгортка - Header і LegacyWidget залишаються зовні function App() { return ( <div> <Header /> <React.StrictMode> <Sidebar /> <Content /> </React.StrictMode> <LegacyWidget /> </div> ); } ``` ### Типові помилки **Сприймати подвійні логи як баг.** Dev-консоль показує "Render" двічі. Це правильно. Продакшн показує один раз. Виправлення - не прибирати StrictMode. **Мутувати спільні об'єкти під час рендеру.** Саме для цього StrictMode і існує. ```jsx // Баг: спільний об'єкт мутується під час рендеру const config = { count: 0 }; function BadComponent() { config.count++; // Запускається двічі в dev, тому count = 2 після першого видимого рендеру return <div>{config.count}</div>; } // Виправлення: значення береться зі стану function GoodComponent() { const [count, setCount] = useState(0); return <div>{count}</div>; } ``` **Ефекти без правильного cleanup.** React 18+ StrictMode запускає cleanup перед другим setup. Неповний cleanup ламає другий прохід. ```jsx // Баг: два інтервали запускаються при монтажі в dev, бо cleanup відсутній useEffect(() => { const id = setInterval(() => setCount(c => c + 1), 1000); // немає cleanup }, []); // Виправлення: cleanup робить ефект ідемпотентним useEffect(() => { const id = setInterval(() => setCount(c => c + 1), 1000); return () => clearInterval(id); }, []); ``` **Прибирати StrictMode, щоб замовкнути попередження сторонніх бібліотек.** Це ховає реальні проблеми замість їх вирішення. Перенеси "шумну" бібліотеку за межі обгортки і залиш баг-репорт авторам. **Розраховувати на поведінку StrictMode в Jest-тестах.** Jest рендерить компоненти один раз за замовчуванням. Щоб тестувати в умовах StrictMode, огорни явно. ```jsx // setupTests.js import React from 'react'; import { render } from '@testing-library/react'; export function renderStrict(ui) { return render(<React.StrictMode>{ui}</React.StrictMode>); } ``` ### Де зустрічається - **Vite + React** і **Create React App**: обидва шаблони за замовчуванням огортають корінь у `<StrictMode>` - **Next.js**: підключається через `reactStrictMode: true` в `next.config.js`; за замовчуванням вимкнено - **Storybook**: огортай stories у StrictMode, щоб знаходити проблеми з HOC раніше - **Redux Toolkit**: подвійні виклики безпечні, бо RTK-селектори є чистими функціями за дизайном В кодобазах часто зустрічається такий патерн: fetch спрацьовує двічі в dev і команда прибирає StrictMode замість того, щоб додати `AbortController`. Додавання контролера виправляє і поведінку в StrictMode, і реальний memory leak, який вже існував у продакшні, але був непомітний. ### Питання на співбесіді **Q:** Чому React 18+ StrictMode запускає ефекти двічі при монтажі? **A:** Це симулює конкурентні фічі, де React може демонтувати і перемонтувати компонент для звільнення ресурсів. Якщо setup запускається двічі без того, щоб cleanup нейтралізував перший запуск, в конкурентному режимі виникають реальні баги. **Q:** Чи гальмує StrictMode продакшн? **A:** Ні. React повністю прибирає його через tree-shaking у продакшні. Подвійні виклики існують тільки в dev-білдах. **Q:** Стороння бібліотека видає попередження в StrictMode. Що робити? **A:** Перевір, чи є новіша версія бібліотеки з підтримкою React 18. Якщо ні, перенеси цей компонент за межі StrictMode і залиш баг-репорт авторам. Не вимикай StrictMode для всього додатку. **Q:** Що замінило рядкові refs? **A:** `useRef` у функціональних компонентах, `React.createRef()` у класових. Рядкові refs прибрані в React 19. **Q (senior):** Як StrictMode взаємодіє з React Compiler у canary-білдах? **A:** React Compiler автоматично мемоїзує компоненти, вважаючи рендери чистими. Подвійний виклик StrictMode перевіряє це припущення: якщо компонент повертає різний результат на другому рендері, компілятор не може безпечно кешувати його. Так StrictMode виявляє нечисті функції, які компілятор інакше міг би непомітно оптимізувати неправильно. ## Приклади ### Чистий проти нечистого рендеру під StrictMode ```jsx // Чиста функція: виживає при подвійному рендері, обидва логи однакові function Greeting({ name }) { const msg = `Hello, ${name}`; // обчислюється тільки з props console.log(msg); // "Hello, World" x2 в dev - обидва однакові return <div>{msg}</div>; } // Нечиста функція: подвійний рендер виявляє мутацію let calls = 0; function ImpureGreeting({ name }) { calls++; // мутує зовнішню змінну під час рендеру console.log(`Call #${calls}: Hello, ${name}`); // Dev: "Call #1: Hello, World" потім "Call #2: Hello, World" // calls = 2 після одного видимого рендеру - справжній баг return <div>Hello, {name}</div>; } ``` `Greeting` виводить однаковий рядок двічі, бо функція чиста. `ImpureGreeting` робить мутацію очевидною: `calls` дорівнює 2 після того, що користувач бачить як один рендер. Саме для цього все і затівалось. ### Fetch з cleanup через AbortController ```jsx // UserList.jsx import { useState, useEffect } from 'react'; function UserList() { const [users, setUsers] = useState([]); useEffect(() => { const controller = new AbortController(); fetch('https://jsonplaceholder.typicode.com/users', { signal: controller.signal, }) .then(res => res.json()) .then(data => setUsers(data)) .catch(err => { if (err.name !== 'AbortError') console.error(err); }); return () => controller.abort(); // cleanup скасовує перший запит }, []); return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>; } ``` Без `AbortController` StrictMode запускає два запити при монтажі в dev і обидва намагаються викликати `setUsers`. З cleanup перший запит скасовується і завершується тільки другий. У продакшні - один запит. Той самий код, правильна поведінка в обох середовищах. ### Застарілі методи життєвого циклу - до і після ```jsx // До: три методи викликають попередження StrictMode class UserProfile extends React.Component { componentWillMount() { // Попередження: використовуй componentDidMount this.setState({ loading: true }); } componentWillReceiveProps(nextProps) { // Попередження: використовуй getDerivedStateFromProps if (nextProps.userId !== this.props.userId) { this.setState({ loading: true }); } } render() { return <div>{this.state.loading ? 'Loading...' : this.props.userId}</div>; } } // Після: класовий компонент без попереджень StrictMode class UserProfile extends React.Component { static getDerivedStateFromProps(props, state) { if (props.userId !== state.prevUserId) { return { loading: true, prevUserId: props.userId }; } return null; } componentDidMount() { this.loadUser(this.props.userId); } componentDidUpdate(prevProps) { if (prevProps.userId !== this.props.userId) { this.loadUser(this.props.userId); } } loadUser(id) { // логіка завантаження this.setState({ loading: false }); } render() { return <div>{this.state.loading ? 'Loading...' : this.props.userId}</div>; } } ``` `componentWillMount` і `componentWillReceiveProps` позначені як `UNSAFE_` з React 16.3 і будуть прибрані в наступній мажорній версії. StrictMode робить це видимим зараз, щоб ти встиг мігрувати до дедлайну.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.