<StrictMode>

<StrictMode> дозволяє знаходити типові помилки у ваших компонентах на ранній стадії розробки.

<StrictMode>
  <App />
</StrictMode>

Довідник

<StrictMode>

Використовуйте StrictMode, щоб увімкнути додаткову поведінку розробки та попередження для дерева компонентів всередині:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

Дивіться більше прикладів нижче.

Суворий режим уможливлює таку поведінку лише для розробки:

Пропси

StrictMode не приймає жодних пропсів.

Застереження

  • Всередині дерева, обгорнутого у <StrictMode>, не існує способу відмовитися від суворого режиму. Це дає вам впевненість у тому, що всі компоненти всередині <StrictMode> буде перевірено. Якщо дві команди, які працюють над продуктом, не можуть дійти згоди щодо цінності перевірок, їм слід або досягти консенсусу, або перемістити <StrictMode> вниз по дереву.

Використання

Увімкнення суворого режиму для всієї програми

Суворий режим вмикає додаткові перевірки, призначені лише для розробки, для всього дерева компонентів всередині компонента <StrictMode>. Ці перевірки допоможуть вам знайти типові вади у ваших компонентах на ранніх стадіях процесу розробки.

Щоб увімкнути Суворий режим для всього вашого застосунку, обгорніть ваш кореневий компонент <StrictMode> під час рендерингу:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

Ми рекомендуємо обгортати весь ваш застосунок у строгий режим, особливо для новостворених програм. Якщо ви використовуєте фреймворк, який викликає createRoot, зверніться до його документації, щоб дізнатися, як увімкнути Суворий режим.

Хоча перевірки у суворому режимі виконуються лише під час розробки, вони допомагають знайти проблеми, які вже існують у вашому коді, але можуть бути складними для надійного відтворення у виробництві. Суворий режим дозволяє виправляти вади до того, як про них повідомлять користувачі.

Суворий режим уможливлює такі перевірки під час розробки:

Усі ці перевірки призначені лише для розробки і не впливають на продуктивну збірку.


Увімкнення суворого режиму для частини програми

Ви також можете увімкнути Суворий режим для будь-якої частини вашої програми:

import { StrictMode } from 'react';

function App() {
  return (
    <>
      <Header />
      <StrictMode>
        <main>
          <Sidebar />
          <Content />
        </main>
      </StrictMode>
      <Footer />
    </>
  );
}

У цьому прикладі перевірки суворого режиму не буде виконано для компонентів Header та Footer. Однак вони будуть виконані для Sidebar і Content, а також для всіх компонентів всередині них, незалежно від глибини.


Виправлення помилок, знайдених при подвійному рендерингу у розробці

React вважає, що кожен компонент, який ви пишете, є чистою функцією. Це означає, що React-компоненти, які ви пишете, повинні завжди повертати той самий JSX при тих самих вхідних даних (пропси, стан та контекст).

Компоненти, що порушують це правило, поводяться непередбачувано і спричиняють помилки. Щоб допомогти вам знайти випадково нечистий код, Суворий режим викликає деякі ваші функції (лише ті, що мають бути чистими) двічі під час розробки. Сюди входять:

Якщо функція є чистою, запуск її двічі не змінює її поведінки, оскільки чиста функція щоразу видає однаковий результат. Однак, якщо функція є нечистою (наприклад, вона мутує дані, які отримує), її повторний запуск, як правило, помітний (саме це і робить її нечистою!) Це допоможе вам виявити та виправити проблему на ранніх стадіях.

Приклад, який ілюструє, як подвійний рендеринг у суворому режимі допомагає знаходити проблеми на ранніх стадіях.

Цей StoryTray компонент бере масив сторінок і додає наприкінці останній елемент "Створити історію":

import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(<App />);
import { useState } from 'react';
import StoryTray from './StoryTray.js';

let initialStories = [
  {id: 0, label: "Ankit's Story" },
  {id: 1, label: "Taylor's Story" },
];

export default function App() {
  let [stories, setStories] = useState(initialStories)
  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        textAlign: 'center',
      }}
    >
      <StoryTray stories={stories} />
    </div>
  );
}
export default function StoryTray({ stories }) {
  const items = stories;
  items.push({ id: 'create', label: 'Create Story' });
  return (
    <ul>
      {items.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}
ul {
  margin: 0;
  list-style-type: none;
  height: 100%;
}

li {
  border: 1px solid #aaa;
  border-radius: 6px;
  float: left;
  margin: 5px;
  margin-bottom: 20px;
  padding: 5px;
  width: 70px;
  height: 100px;
}

У вищенаведеному коді є помилка. Однак її легко не помітити, оскільки початкове виведення виглядає коректно.

Ця помилка стане більш помітною, якщо компонент StoryTray повторно відрендерити декілька разів. Наприклад, давайте зробимо так, щоб StoryTray повторно рендерився з іншим кольором фону при наведенні на нього:

import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
import { useState } from 'react';
import StoryTray from './StoryTray.js';

let initialStories = [
  {id: 0, label: "Ankit's Story" },
  {id: 1, label: "Taylor's Story" },
];

export default function App() {
  let [stories, setStories] = useState(initialStories)
  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        textAlign: 'center',
      }}
    >
      <StoryTray stories={stories} />
    </div>
  );
}
import { useState } from 'react';

export default function StoryTray({ stories }) {
  const [isHover, setIsHover] = useState(false);
  const items = stories;
  items.push({ id: 'create', label: 'Create Story' });
  return (
    <ul
      onPointerEnter={() => setIsHover(true)}
      onPointerLeave={() => setIsHover(false)}
      style={{
        backgroundColor: isHover ? '#ddd' : '#fff'
      }}
    >
      {items.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}
ul {
  margin: 0;
  list-style-type: none;
  height: 100%;
}

li {
  border: 1px solid #aaa;
  border-radius: 6px;
  float: left;
  margin: 5px;
  margin-bottom: 20px;
  padding: 5px;
  width: 70px;
  height: 100px;
}

Помітьте, що кожного разу, коли ви наводите курсор миші на компонент StoryTray, до списку знову додається "Створити історію". За задумом, код повинен був додавати його один раз в кінці. Але StoryTray безпосередньо змінює масив stories з пропсів. Кожного разу, коли StoryTray рендерить, він знову додає "Створити історію" в кінець того ж масиву. Іншими словами, StoryTray не є чистою функцією - її багаторазовий запуск призводить до різних результатів.

Щоб виправити цю проблему, ви можете зробити копію масиву і модифікувати цю копію замість оригіналу:

export default function StoryTray({ stories }) {
  const items = stories.slice(); // Clone the array
  // ✅ Good: Pushing into a new array
  items.push({ id: 'create', label: 'Create Story' });

Це зробить функцію StoryTray чистою. Кожного разу, коли вона викликається, вона змінюватиме лише нову копію масиву і не впливатиме на зовнішні об'єкти або змінні. Це вирішило проблему, але вам довелося змусити компонент рендерити повторно частіше, перш ніж стало очевидно, що з його поведінкою щось не так.

В оригінальному прикладі баг не був очевидним. Тепер давайте обгорнемо оригінальний (дефектний) код у <StrictMode>:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
import { useState } from 'react';
import StoryTray from './StoryTray.js';

let initialStories = [
  {id: 0, label: "Ankit's Story" },
  {id: 1, label: "Taylor's Story" },
];

export default function App() {
  let [stories, setStories] = useState(initialStories)
  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        textAlign: 'center',
      }}
    >
      <StoryTray stories={stories} />
    </div>
  );
}
export default function StoryTray({ stories }) {
  const items = stories;
  items.push({ id: 'create', label: 'Create Story' });
  return (
    <ul>
      {items.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}
ul {
  margin: 0;
  list-style-type: none;
  height: 100%;
}

li {
  border: 1px solid #aaa;
  border-radius: 6px;
  float: left;
  margin: 5px;
  margin-bottom: 20px;
  padding: 5px;
  width: 70px;
  height: 100px;
}

Суворий режим завжди викликає вашу функцію рендерингу двічі, тому ви можете одразу побачити помилку ("Create Story" з'являється двічі). Це дозволяє помітити такі помилки на ранній стадії процесу. Коли ви виправляєте компонент для рендерингу у суворому режимі, ви такожвиправляєте багато можливих майбутніх виробничих помилок, таких як функціональність наведення курсору миші

та ін.:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
import { useState } from 'react';
import StoryTray from './StoryTray.js';

let initialStories = [
  {id: 0, label: "Ankit's Story" },
  {id: 1, label: "Taylor's Story" },
];

export default function App() {
  let [stories, setStories] = useState(initialStories)
  return (
    <div
      style={{
        width: '100%',
        height: '100%',
        textAlign: 'center',
      }}
    >
      <StoryTray stories={stories} />
    </div>
  );
}
import { useState } from 'react';

export default function StoryTray({ stories }) {
  const [isHover, setIsHover] = useState(false);
  const items = stories.slice(); // Clone the array
  items.push({ id: 'create', label: 'Create Story' });
  return (
    <ul
      onPointerEnter={() => setIsHover(true)}
      onPointerLeave={() => setIsHover(false)}
      style={{
        backgroundColor: isHover ? '#ddd' : '#fff'
      }}
    >
      {items.map(story => (
        <li key={story.id}>
          {story.label}
        </li>
      ))}
    </ul>
  );
}
ul {
  margin: 0;
  list-style-type: none;
  height: 100%;
}

li {
  border: 1px solid #aaa;
  border-radius: 6px;
  float: left;
  margin: 5px;
  margin-bottom: 20px;
  padding: 5px;
  width: 70px;
  height: 100px;
}

Без суворого режиму було легко не помітити ваду, доки ви не додали більше рендерів. У суворому режимі той самий вада з'являється одразу. Суворий режим допомагає знаходити вади до того, як ви покажете їх команді та користувачам.

Детальніше про чистоту компонентів.

Якщо у вас встановлено React DevTools, будь-які виклики console.log під час другого виклику рендерингу будуть виглядати трохи затемненими. React DevTools також пропонує налаштування (за замовчуванням вимкнене) для їх повного придушення.


Виправлення вад, виявлених при повторному запуску Effects у розробці

Суворий режим також може допомогти знайти проблеми у ефектах.

Кожен ефект має деякий код налаштування та може мати деякий код очищення. Зазвичай React викликає setup, коли компонент монтується (додається на екран) і викликає cleanup, коли компонент демонтується (видаляється з екрану). Потім React викликає очищення і налаштування знову, якщо його залежності змінилися з моменту останнього рендерингу.

Коли увімкнено Суворий режим, React також виконує один додатковий цикл налаштування+очищення у розробці для кожного ефекту. Це може здатися дивним, але це допомагає виявити ледь помітні вади, які важко зловити вручну.

Цей приклад ілюструє, як повторний запуск ефектів у суворому режимі допомагає виявити проблеми на ранніх стадіях.

Розглянемо приклад, який підключає компонент до чату:

import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(<App />);
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';
const roomId = 'general';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  }, []);
  return <h1>Welcome to the {roomId} room!</h1>;
}
let connections = 0;

export function createConnection(serverUrl, roomId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
      connections++;
      console.log('Active connections: ' + connections);
    },
    disconnect() {
      console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl);
      connections--;
      console.log('Active connections: ' + connections);
    }
  };
}
input { display: block; margin-bottom: 20px; }
button { margin-left: 10px; }

У цьому коді є проблема, але вона може бути не одразу зрозумілою.

Щоб зробити проблему більш очевидною, давайте реалізуємо функцію. У наведеному нижче прикладі roomId не є жорстко закодованим. Замість цього користувач може вибрати roomId, до якого він хоче підключитися, зі спадного списку. Натисніть "Відкрити чат", а потім оберіть різні чати по черзі. Відстежуйте кількість активних з'єднань в консолі:

import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(<App />);
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  }, [roomId]);

  return <h1>Welcome to the {roomId} room!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}
let connections = 0;

export function createConnection(serverUrl, roomId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
      connections++;
      console.log('Active connections: ' + connections);
    },
    disconnect() {
      console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl);
      connections--;
      console.log('Active connections: ' + connections);
    }
  };
}
input { display: block; margin-bottom: 20px; }
button { margin-left: 10px; }

Ви помітите, що кількість відкритих з'єднань постійно зростає. У реальному застосунку це спричинить проблеми з продуктивністю та мережею. Проблема у тому, що вашому ефекту бракує функції очищення:

useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

Тепер, коли ваш ефект "прибрав" за собою і знищив застарілі зв'язки, витік вирішено. Однак зауважте, що проблему не було видно, доки ви не додали більше функцій (поле вибору).

В оригінальному прикладі баг не був очевидним. Тепер давайте обгорнемо оригінальний (дефектний) код у <StrictMode>:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';
const roomId = 'general';

export default function ChatRoom() {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  }, []);
  return <h1>Welcome to the {roomId} room!</h1>;
}
let connections = 0;

export function createConnection(serverUrl, roomId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
      connections++;
      console.log('Active connections: ' + connections);
    },
    disconnect() {
      console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl);
      connections--;
      console.log('Active connections: ' + connections);
    }
  };
}
input { display: block; margin-bottom: 20px; }
button { margin-left: 10px; }

У суворому режимі одразу видно, що є проблема (кількість активних з'єднань підскакує до 2). Суворий режим виконує додатковий цикл налаштування+очищення для кожного ефекту. Цей ефект не має логіки очищення, тому він створює додаткове з'єднання, але не знищує його. Це підказка, що вам бракує функції очищення.

Суворий режим дозволяє помітити такі помилки на ранніх стадіях процесу. Коли ви виправляєте свій ефект, додаючи функцію очищення у суворому режимі, ви такожвиправляєте багато можливих майбутніх виробничих помилок, таких як поле вибору з попередньої версії:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
import { useState, useEffect } from 'react';
import { createConnection } from './chat.js';

const serverUrl = 'https://localhost:1234';

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () => connection.disconnect();
  }, [roomId]);

  return <h1>Welcome to the {roomId} room!</h1>;
}

export default function App() {
  const [roomId, setRoomId] = useState('general');
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the chat room:{' '}
        <select
          value={roomId}
          onChange={e => setRoomId(e.target.value)}
        >
          <option value="general">general</option>
          <option value="travel">travel</option>
          <option value="music">music</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>
        {show ? 'Close chat' : 'Open chat'}
      </button>
      {show && <hr />}
      {show && <ChatRoom roomId={roomId} />}
    </>
  );
}
let connections = 0;

export function createConnection(serverUrl, roomId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log('✅ Connecting to "' + roomId + '" room at ' + serverUrl + '...');
      connections++;
      console.log('Active connections: ' + connections);
    },
    disconnect() {
      console.log('❌ Disconnected from "' + roomId + '" room at ' + serverUrl);
      connections--;
      console.log('Active connections: ' + connections);
    }
  };
}
input { display: block; margin-bottom: 20px; }
button { margin-left: 10px; }

Зверніть увагу, що кількість активних з'єднань у консолі більше не зростає.

Без суворого режиму легко не помітити, що ваш ефект потребує очищення. Виконавши setup → cleanup → setup замість setup для вашого ефекту у розробці, Строгий режим зробив відсутню логіку очищення більш помітною.

Довідка про застосування очищення ефектів.


Виправлення попереджень про застарілість, увімкнених у строгому режимі

React попереджає, якщо якийсь компонент всередині дерева <StrictMode> використовує один з цих застарілих API:

Ці API переважно використовуються у старих компонентах класу , тому вони рідко з'являються у сучасних програмах.