<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 组件必须始终在给定相同输入(props、状态和上下文)的情况下返回相同的 JSX。

违反此规则的组件的行为不可预测,并会导致错误。为了帮助您查找意外的不纯代码,严格模式会在开发环境中两次调用一些函数(仅应为纯函数的那些函数)。这包括:

如果一个函数是纯函数,则运行它两次不会改变其行为,因为纯函数每次都会产生相同的结果。但是,如果一个函数是不纯的(例如,它会改变它接收的数据),则运行它两次往往会很明显(这就是它不纯的原因!)。这有助于您尽早发现并修复错误。

以下是一个示例,说明严格模式中的双重渲染如何帮助您尽早发现错误。

这个`StoryTray组件接受一个`stories数组,并在最后添加一个“创建故事”项。

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组件上时,使用不同的背景颜色重新渲染它。

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组件上时,“创建故事”都会再次添加到列表中。代码的本意是一次性将其添加到结尾。但是StoryTray直接修改了来自props的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函数成为纯函数。每次调用它时,它只会修改数组的新副本,而不会影响任何外部对象或变量。这解决了bug,但是你必须让组件更频繁地重新渲染才能看出其行为有什么问题。

在原始示例中,这个bug并不明显。现在让我们将原始(有bug的)代码包装在<StrictMode>中。

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>
  );
}

严格模式总是会调用你的渲染函数两次,这样你就可以立即看到错误(“创建故事”出现了两次)。这使你能够尽早发现此类错误。当你修复组件使其在严格模式下渲染时,你修复了许多可能出现的未来生产错误,例如之前的悬停功能。

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>
  );
}

在没有严格模式的情况下,很容易错过这个bug,直到你添加更多重新渲染。严格模式使同样的bug立即显现。严格模式可以帮助你尽早发现bug,避免将其提交给你的团队和用户。

阅读更多关于保持组件纯净的内容。

注意

如果你安装了React DevTools,则在第二次渲染调用期间的任何console.log调用都会显示为略微暗淡。React DevTools 还提供一个设置(默认情况下关闭),可以完全禁止它们。


修复在开发过程中重新运行 Effects 发现的bug

严格模式还可以帮助发现Effects中的bug。

每个Effect都有一些设置代码,也可能有一些清理代码。通常,React会在组件挂载(添加到屏幕上)时调用设置,并在组件卸载(从屏幕上移除)时调用清理。如果自上次渲染以来其依赖项发生了更改,React 则会再次调用清理和设置。

启用严格模式后,React还会在开发环境中为每个Effect运行一个额外的设置+清理周期。这可能令人惊讶,但它有助于发现难以手动捕获的细微错误。

以下是一个示例,说明如何在严格模式下重新运行 Effects 有助于尽早发现错误。

考虑这个将组件连接到聊天的示例。

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

import App from './App';

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

这段代码存在问题,但可能并不立即显而易见。

为了使问题更明显,让我们实现一个功能。在下面的示例中,roomId不是硬编码的。用户可以从下拉菜单中选择他们想要连接的roomId。点击“打开聊天”,然后依次选择不同的聊天室。跟踪控制台中活动连接的数量。

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

import App from './App';

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

你会注意到,打开的连接数总是不断增长。在真实的应用程序中,这会导致性能和网络问题。问题在于你的Effect缺少清理函数:

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

现在你的Effect“清理”自身并销毁过时的连接,泄漏问题就解决了。但是,请注意,这个问题直到你添加更多功能(选择框)后才变得可见。

在原始示例中,这个bug并不明显。现在让我们将原始(有bug的)代码包装在<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>
);

使用严格模式,你会立即看到存在问题(活动连接数跳到2)。严格模式为每个Effect运行额外的设置+清理周期。此Effect没有清理逻辑,因此它创建了一个额外的连接,但不会销毁它。这暗示你缺少清理函数。

严格模式使你能够尽早发现此类错误。当你通过在严格模式下添加清理函数来修复Effect时,你修复了许多可能出现的未来生产错误,例如之前的选择框。

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>
);

注意控制台中的活动连接计数不再持续增长。

在没有严格模式的情况下,很容易错过你的Effect需要清理。通过在开发环境中为你的Effect运行设置→清理→设置而不是设置,严格模式使缺失的清理逻辑更易于察觉。

阅读更多关于实现Effect清理的内容。


修复在开发过程中重新运行ref回调发现的bug

严格模式还可以帮助发现回调ref中的bug。

每个回调ref都有一些设置代码,也可能有一些清理代码。通常,React会在元素创建(添加到DOM)时调用设置,并在元素移除(从DOM移除)时调用清理。

启用严格模式后,React还会在开发环境中为每个回调ref运行一个额外的设置+清理周期。这可能令人惊讶,但它有助于发现难以手动捕获的细微错误。

考虑这个示例,它允许你选择一种动物,然后滚动到其中一种动物。注意,当你从“猫”切换到“狗”时,控制台日志显示列表中的动物数量不断增加,“滚动到”按钮停止工作。

import { useRef, useState } from "react";

export default function AnimalFriends() {
  const itemsRef = useRef([]);
  const [animalList, setAnimalList] = useState(setupAnimalList);
  const [animal, setAnimal] = useState('cat');

  function scrollToAnimal(index) {
    const list = itemsRef.current;
    const {node} = list[index];
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }
  
  const animals = animalList.filter(a => a.type === animal)
  
  return (
    <>
      <nav>
        <button onClick={() => setAnimal('cat')}>Cats</button>
        <button onClick={() => setAnimal('dog')}>Dogs</button>
      </nav>
      <hr />
      <nav>
        <span>Scroll to:</span>{animals.map((animal, index) => (
          <button key={animal.src} onClick={() => scrollToAnimal(index)}>
            {index}
          </button>
        ))}
      </nav>
      <div>
        <ul>
          {animals.map((animal) => (
              <li
                key={animal.src}
                ref={(node) => {
                  const list = itemsRef.current;
                  const item = {animal: animal, node}; 
                  list.push(item);
                  console.log(`✅ Adding animal to the map. Total animals: ${list.length}`);
                  if (list.length > 10) {
                    console.log('❌ Too many animals in the list!');
                  }
                  return () => {
                    // 🚩 No cleanup, this is a bug!
                  }
                }}
              >
                <img src={animal.src} />
              </li>
            ))}
          
        </ul>
      </div>
    </>
  );
}

function setupAnimalList() {
  const animalList = [];
  for (let i = 0; i < 10; i++) {
    animalList.push({type: 'cat', src: "https://loremflickr.com/320/240/cat?lock=" + i});
  }
  for (let i = 0; i < 10; i++) {
    animalList.push({type: 'dog', src: "https://loremflickr.com/320/240/dog?lock=" + i});
  }

  return animalList;
}

这是一个生产环境bug!由于ref回调在清理时没有从列表中删除动物,因此动物列表不断增长。这是一个内存泄漏,会在真实的应用程序中导致性能问题,并破坏应用程序的行为。

问题在于ref回调没有自行清理。

<li
ref={node => {
const list = itemsRef.current;
const item = {animal, node};
list.push(item);
return () => {
// 🚩 No cleanup, this is a bug!
}
}}
</li>

现在让我们将原始(有bug的)代码包装在<StrictMode>中。

import { useRef, useState } from "react";

export default function AnimalFriends() {
  const itemsRef = useRef([]);
  const [animalList, setAnimalList] = useState(setupAnimalList);
  const [animal, setAnimal] = useState('cat');

  function scrollToAnimal(index) {
    const list = itemsRef.current;
    const {node} = list[index];
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }
  
  const animals = animalList.filter(a => a.type === animal)
  
  return (
    <>
      <nav>
        <button onClick={() => setAnimal('cat')}>Cats</button>
        <button onClick={() => setAnimal('dog')}>Dogs</button>
      </nav>
      <hr />
      <nav>
        <span>Scroll to:</span>{animals.map((animal, index) => (
          <button key={animal.src} onClick={() => scrollToAnimal(index)}>
            {index}
          </button>
        ))}
      </nav>
      <div>
        <ul>
          {animals.map((animal) => (
              <li
                key={animal.src}
                ref={(node) => {
                  const list = itemsRef.current;
                  const item = {animal: animal, node} 
                  list.push(item);
                  console.log(`✅ Adding animal to the map. Total animals: ${list.length}`);
                  if (list.length > 10) {
                    console.log('❌ Too many animals in the list!');
                  }
                  return () => {
                    // 🚩 No cleanup, this is a bug!
                  }
                }}
              >
                <img src={animal.src} />
              </li>
            ))}
          
        </ul>
      </div>
    </>
  );
}

function setupAnimalList() {
  const animalList = [];
  for (let i = 0; i < 10; i++) {
    animalList.push({type: 'cat', src: "https://loremflickr.com/320/240/cat?lock=" + i});
  }
  for (let i = 0; i < 10; i++) {
    animalList.push({type: 'dog', src: "https://loremflickr.com/320/240/dog?lock=" + i});
  }

  return animalList;
}

使用严格模式,你会立即看到存在问题。严格模式为每个回调ref运行额外的设置+清理周期。此回调ref没有清理逻辑,因此它添加了ref但没有删除它们。这暗示你缺少清理函数。

严格模式使你能够及早发现回调ref中的错误。当你通过在严格模式下添加清理函数来修复回调时,你修复了许多可能出现的未来生产错误,例如之前的“滚动到”bug。

import { useRef, useState } from "react";

export default function AnimalFriends() {
  const itemsRef = useRef([]);
  const [animalList, setAnimalList] = useState(setupAnimalList);
  const [animal, setAnimal] = useState('cat');

  function scrollToAnimal(index) {
    const list = itemsRef.current;
    const {node} = list[index];
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }
  
  const animals = animalList.filter(a => a.type === animal)
  
  return (
    <>
      <nav>
        <button onClick={() => setAnimal('cat')}>Cats</button>
        <button onClick={() => setAnimal('dog')}>Dogs</button>
      </nav>
      <hr />
      <nav>
        <span>Scroll to:</span>{animals.map((animal, index) => (
          <button key={animal.src} onClick={() => scrollToAnimal(index)}>
            {index}
          </button>
        ))}
      </nav>
      <div>
        <ul>
          {animals.map((animal) => (
              <li
                key={animal.src}
                ref={(node) => {
                  const list = itemsRef.current;
                  const item = {animal, node};
                  list.push({animal: animal, node});
                  console.log(`✅ Adding animal to the map. Total animals: ${list.length}`);
                  if (list.length > 10) {
                    console.log('❌ Too many animals in the list!');
                  }
                  return () => {
                    list.splice(list.indexOf(item));
                    console.log(`❌ Removing animal from the map. Total animals: ${itemsRef.current.length}`);
                  }
                }}
              >
                <img src={animal.src} />
              </li>
            ))}
          
        </ul>
      </div>
    </>
  );
}

function setupAnimalList() {
  const animalList = [];
  for (let i = 0; i < 10; i++) {
    animalList.push({type: 'cat', src: "https://loremflickr.com/320/240/cat?lock=" + i});
  }
  for (let i = 0; i < 10; i++) {
    animalList.push({type: 'dog', src: "https://loremflickr.com/320/240/dog?lock=" + i});
  }

  return animalList;
}

现在在严格模式下的初始挂载中,ref回调都被设置、清理,然后再次设置。

...
Adding animal to the map. Total animals: 10
...
Removing animal from the map. Total animals: 0
...
Adding animal to the map. Total animals: 10

这是预期的。严格模式确认ref回调已正确清理,因此大小永远不会超过预期数量。修复后,没有内存泄漏,所有功能都按预期工作。

在没有严格模式的情况下,很容易错过这个bug,直到你点击应用程序周围才能注意到功能损坏。严格模式使bug立即出现,在你将其推送到生产环境之前。


修复严格模式启用的弃用警告

如果`<StrictMode>树中的任何组件使用以下任何已弃用的API,React将发出警告。

这些API主要用于较旧的 类组件,因此它们很少出现在现代应用程序中。