Перейти к содержанию

StrictMode

Режим <StrictMode> позволяет находить распространенные ошибки в компонентах на ранних стадиях разработки.

1
2
3
<StrictMode>
    <App />
</StrictMode>

Описание

<StrictMode>

Используйте StrictMode для включения дополнительных поведений разработки и предупреждений для внутреннего дерева компонентов:

1
2
3
4
5
6
7
8
9
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

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

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

  • Ваши компоненты будут перерендериваться дополнительно для поиска ошибок, вызванных нечистым рендерингом.
  • Ваши компоненты будут перезапускать эффекты дополнительно, чтобы найти ошибки, вызванные отсутствием очистки эффектов.
  • Ваши компоненты будут проверяться на использование устаревших API.

Пропсы

StrictMode не принимает никаких пропсов.

Предупреждения

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

Использование

Включение строгого режима для всего приложения

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

Чтобы включить режим Strict Mode для всего приложения, оберните корневой компонент компонентом <StrictMode> при его рендеринге:

1
2
3
4
5
6
7
8
9
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';

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

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

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

Проверки строгого режима

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

  • Ваши компоненты будут перерендериваться дополнительно для поиска ошибок, вызванных нечистым рендерингом.
  • Ваши компоненты будут перезапускать эффекты дополнительно, чтобы найти ошибки, вызванные отсутствием очистки эффектов.
  • Ваши компоненты будут проверяться на использование устаревших API.

Все эти проверки предназначены только для разработки и не влияют на производственную сборку.

Включение строгого режима для части приложения

Вы также можете включить строгий режим для любой части вашего приложения:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { StrictMode } from 'react';

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

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

Исправление ошибок, найденных при двойном рендеринге в разработке

React предполагает, что каждый написанный вами компонент является чистой функцией Это означает, что написанные вами компоненты React должны всегда возвращать один и тот же JSX при одинаковых входных данных (props, state и context).

Компоненты, нарушающие это правило, ведут себя непредсказуемо и вызывают ошибки. Чтобы помочь вам найти случайно нечистый код, Strict Mode вызывает некоторые из ваших функций (только те, которые должны быть чистыми) дважды в процессе разработки. Это включает в себя:

  • тело функции вашего компонента (только логика верхнего уровня, поэтому сюда не входит код внутри обработчиков событий).
  • Функции, которые вы передаете в useState, set functions, useMemo, или useReducer.
  • Некоторые методы компонентов класса, такие как constructor, render, shouldComponentUpdate (посмотреть весь список).

Если функция чистая, то ее повторный запуск не изменит ее поведения, потому что чистая функция каждый раз выдает один и тот же результат. Однако, если функция нечистая (например, она мутирует полученные данные), ее повторный запуск будет заметен (вот что делает ее нечистой!) Это поможет вам обнаружить и исправить ошибку на ранней стадии.

Здесь приведен пример, иллюстрирующий, как двойной рендеринг в строгом режиме помогает найти ошибку на ранней стадии.

Этот компонент StoryTray берет массив историй и добавляет последний элемент "Create Story" в конце:

1
2
3
4
5
6
7
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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>
    );
}

В приведенном выше коде есть ошибка. Однако ее легко не заметить, поскольку первоначальный вывод выглядит правильным.

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

1
2
3
4
5
6
7
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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>
    );
}

Обратите внимание, что каждый раз, когда вы наводите курсор на компонент StoryTray, "Create Story" снова добавляется в список. Замысел кода заключался в том, чтобы добавить его один раз в конце. Но StoryTray напрямую изменяет массив stories из пропса. Каждый раз, когда StoryTray рендерит, он снова добавляет "Create Story" в конец того же массива. Другими словами, StoryTray не является чистой функцией - ее многократный запуск приводит к различным результатам.

Чтобы решить эту проблему, вы можете сделать копию массива и изменить эту копию вместо оригинальной:

1
2
3
4
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>:.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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>
);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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>
    );
}

Строгий режим всегда вызывает вашу функцию рендеринга дважды, чтобы вы могли сразу увидеть ошибку ("Create Story" появляется дважды). Это позволяет заметить такие ошибки на ранней стадии процесса. Когда вы исправляете свой компонент для рендеринга в строгом режиме, вы также исправляете многие возможные будущие ошибки производства, такие как функциональность hover, о которой говорилось ранее:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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>
);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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>
    );
}

Без режима Strict Mode ошибку легко было не заметить, пока вы не добавляли больше рендеров. В режиме Strict Mode та же ошибка появлялась сразу же. Режим Strict Mode помогает найти ошибки до того, как вы передадите их команде и пользователям.

Подробнее о поддержании чистоты компонентов.

React DevTools

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

Исправление ошибок, найденных при повторном запуске эффектов в разработке

Строгий режим также может помочь найти ошибки в Эффектах.

Каждый Эффект имеет некоторый код настройки и может иметь некоторый код очистки. Обычно React вызывает setup, когда компонент mounts (добавляется на экран) и вызывает cleanup, когда компонент unmounts (удаляется с экрана). Затем React снова вызывает cleanup и setup, если его зависимости изменились с момента последнего рендеринга.

Когда включен строгий режим, React также будет выполнять один дополнительный цикл setup+cleanup в процессе разработки для каждого Effect. Это может показаться удивительным, но это помогает выявить тонкие ошибки, которые трудно обнаружить вручную.

Вот пример, иллюстрирующий, как повторный запуск Эффектов в строгом режиме помогает найти ошибки на ранней стадии..

Рассмотрим этот пример, который соединяет компонент с чатом:

1
2
3
4
5
6
7
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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>;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
            );
        },
    };
}

В этом коде есть проблема, но она может быть не сразу понятна.

Чтобы сделать проблему более очевидной, давайте реализуем функцию. В приведенном ниже примере roomId не является жестко закодированным. Вместо этого пользователь может выбрать roomId, к которому он хочет подключиться, из выпадающего списка. Нажмите "Открыть чат", а затем выберите по очереди разные комнаты чата. Следите за количеством активных подключений в консоли:

1
2
3
4
5
6
7
import { createRoot } from 'react-dom/client';
import './styles.css';

import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(<App />);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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} />}
        </>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
            );
        },
    };
}

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

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

Теперь, когда ваш Effect "убирает" за собой и уничтожает устаревшие связи, утечка решена. Однако обратите внимание, что проблема не стала заметной, пока вы не добавили больше возможностей (поле выбора).

В исходном примере ошибка не была очевидной. Теперь давайте обернем исходный (баговый) код в <StrictMode>:.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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>
);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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>;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
            );
        },
    };
}

В строгом режиме вы сразу увидите, что есть проблема (количество активных соединений подскочит до 2). Строгий режим запускает дополнительный цикл настройки+очистки для каждого Эффекта. Этот Эффект не имеет логики очистки, поэтому он создает дополнительное соединение, но не уничтожает его. Это намек на то, что вам не хватает функции очистки.

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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>
);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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} />}
        </>
    );
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
            );
        },
    };
}

Обратите внимание, что количество активных соединений в консоли больше не растет.

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

Подробнее о реализации очистки эффектов

Исправление предупреждений об устаревании, включенных в строгом режиме

React предупреждает, если какой-то компонент в любом месте дерева <StrictMode> использует один из этих устаревших API:

Эти API в основном используются в старых компонентах классов, поэтому они редко появляются в современных приложениях.

Источник — https://react.dev/reference/react/StrictMode

Комментарии