<StrictMode>

<StrictMode> 让你在开发过程中及早发现组件中的常见错误。

<StrictMode>
<App />
</StrictMode>

参考

🌐 Reference

<StrictMode>

使用 StrictMode 启用组件树内部的额外开发行为和警告:

🌐 Use StrictMode to enable additional development behaviors and warnings for the component tree inside:

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

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

查看更多示例。

严格模式启用以下仅限开发的行为:

🌐 Strict Mode enables the following development-only behaviors:

属性

🌐 Props

StrictMode 不接受任何属性。

注意事项

🌐 Caveats

  • 在被 <StrictMode> 封装的树中,没有办法选择退出严格模式。这让你确信 <StrictMode> 内的所有组件都经过检查。如果两个团队在开发同一个产品时对这些检查是否有价值存在分歧,他们需要么达成共识,要么将 <StrictMode> 移动到树的下方。

用法

🌐 Usage

为整个应用启用严格模式

🌐 Enabling Strict Mode for entire app

严格模式为 <StrictMode> 组件内部的整个组件树启用额外的仅开发时检查。这些检查可以帮助你在开发过程中尽早发现组件中的常见错误。

🌐 Strict Mode enables extra development-only checks for the entire component tree inside the <StrictMode> component. These checks help you find common bugs in your components early in the development process.

要为整个应用启用严格模式,请在渲染根组件时用 <StrictMode> 封装它:

🌐 To enable Strict Mode for your entire app, wrap your root component with <StrictMode> when you render it:

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

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

我们建议将整个应用封装在严格模式中,特别是对于新创建的应用。如果你使用的框架为你调用了 createRoot,请查阅其文档以了解如何启用严格模式。

🌐 We recommend wrapping your entire app in Strict Mode, especially for newly created apps. If you use a framework that calls createRoot for you, check its documentation for how to enable Strict Mode.

虽然严格模式检查**仅在开发中运行,**它们可以帮助你发现代码中已经存在但在生产环境中可能难以可靠重现的错误。严格模式让你在用户报告之前修复错误。

🌐 Although the Strict Mode checks only run in development, they help you find bugs that already exist in your code but can be tricky to reliably reproduce in production. Strict Mode lets you fix bugs before your users report them.

注意

严格模式在开发中启用以下检查:

🌐 Strict Mode enables the following checks in development:

所有这些检查仅用于开发,不会影响生产版本。


为应用的一部分启用严格模式

🌐 Enabling Strict Mode for a part of the app

你还可以为应用的任何部分启用严格模式:

🌐 You can also enable Strict Mode for any part of your application:

import { StrictMode } from 'react';

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

在这个例子中,严格模式检查不会针对 HeaderFooter 组件运行。然而,它们会在 SidebarContent 上运行,以及它们内部的所有组件,无论嵌套多深。

🌐 In this example, Strict Mode checks will not run against the Header and Footer components. However, they will run on Sidebar and Content, as well as all of the components inside them, no matter how deep.

注意

当在应用的某个部分启用 StrictMode 时,React 只会启用在生产环境中可能出现的行为。例如,如果在应用根部未启用 <StrictMode>,它将在初始挂载时不会额外重新运行 Effects,因为这会导致子 Effects 在没有父 Effects 的情况下重复触发,而这是在生产环境中不可能发生的。

🌐 When StrictMode is enabled for a part of the app, React will only enable behaviors that are possible in production. For example, if <StrictMode> is not enabled at the root of the app, it will not re-run Effects an extra time on initial mount, since this would cause child effects to double fire without the parent effects, which cannot happen in production.


修复开发中双渲染发现的 bug

🌐 Fixing bugs found by double rendering in development

React 假设你编写的每个组件都是纯函数。 这意味着你编写的 React 组件在相同的输入(props、state 和 context)下必须始终返回相同的 JSX。

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

🌐 Components breaking this rule behave unpredictably and cause bugs. To help you find accidentally impure code, Strict Mode calls some of your functions (only the ones that should be pure) twice in development. This includes:

如果一个函数是纯函数,运行它两次不会改变它的行为,因为纯函数每次都会产生相同的结果。然而,如果一个函数是不纯的(例如,它会修改接收到的数据),运行它两次通常会有明显的差异(这就是它不纯的原因!)这有助于你及早发现并修复错误。

🌐 If a function is pure, running it twice does not change its behavior because a pure function produces the same result every time. However, if a function is impure (for example, it mutates the data it receives), running it twice tends to be noticeable (that’s what makes it impure!) This helps you spot and fix the bug early.

这里有一个示例,说明在严格模式下双重渲染如何帮助你及早发现错误。

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

🌐 This StoryTray component takes an array of stories and adds one last “Create Story” item at the end:

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

上面的代码中有一个错误。然而,很容易忽略它,因为初始输出看起来是正确的。

🌐 There is a mistake in the code above. However, it is easy to miss because the initial output appears correct.

如果 StoryTray 组件重新渲染多次,这个错误会变得更加明显。例如,当你将鼠标悬停在 StoryTray 上时,让 StoryTray 以不同的背景颜色重新渲染:

🌐 This mistake will become more noticeable if the StoryTray component re-renders multiple times. For example, let’s make the StoryTray re-render with a different background color whenever you hover over it:

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 不是一个纯函数——多次运行它会产生不同的结果。

🌐 Notice how every time you hover over the StoryTray component, “Create Story” gets added to the list again. The intention of the code was to add it once at the end. But StoryTray directly modifies the stories array from the props. Every time StoryTray renders, it adds “Create Story” again at the end of the same array. In other words, StoryTray is not a pure function—running it multiple times produces different results.

要解决此问题,你可以制作数组的副本,然后修改该副本而不是原始副本:

🌐 To fix this problem, you can make a copy of the array, and modify that copy instead of the original one:

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 函数变为纯函数。 每次调用它时,它只会修改数组的新副本,而不会影响任何外部对象或变量。这解决了错误,但在问题的行为变得明显之前,你必须让组件更频繁地重新渲染。

🌐 This would make the StoryTray function pure. Each time it is called, it would only modify a new copy of the array, and would not affect any external objects or variables. This solves the bug, but you had to make the component re-render more often before it became obvious that something is wrong with its behavior.

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

严格模式 总是 会调用你的渲染函数两次,因此你可以立即看到错误(“创建故事”出现两次)。这让你在开发早期就能注意到这种错误。当你修复组件以在严格模式下渲染时,你也 同时 修复了许多可能的未来生产环境 bug,比如之前的悬停功能:

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

没有严格模式的话,在你增加更多重新渲染之前,很容易忽略这个错误。严格模式会立即让同样的错误显现出来。严格模式可以帮助你在将错误推送给团队和用户之前发现它们。

🌐 Without Strict Mode, it was easy to miss the bug until you added more re-renders. Strict Mode made the same bug appear right away. Strict Mode helps you find bugs before you push them to your team and to your users.

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

注意

如果你安装了 React DevTools,在第二次渲染调用期间的任何 console.log 调用都会显示为略微变暗。React DevTools 还提供了一个设置(默认关闭)来完全屏蔽它们。

🌐 If you have React DevTools installed, any console.log calls during the second render call will appear slightly dimmed. React DevTools also offers a setting (off by default) to suppress them completely.


修复通过在开发中重新运行副作用发现的错误

🌐 Fixing bugs found by re-running Effects in development

严格模式也可以帮助发现 Effects. 中的错误

🌐 Strict Mode can also help find bugs in Effects.

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

🌐 Every Effect has some setup code and may have some cleanup code. Normally, React calls setup when the component mounts (is added to the screen) and calls cleanup when the component unmounts (is removed from the screen). React then calls cleanup and setup again if its dependencies changed since the last render.

当严格模式开启时,React 还会在开发环境中对每个 Effect 运行 一个额外的设置+清理循环。 这可能会让人感到意外,但它有助于发现难以手动捕捉的微妙错误。

🌐 When Strict Mode is on, React will also run one extra setup+cleanup cycle in development for every Effect. This may feel surprising, but it helps reveal subtle bugs that are hard to catch manually.

这里有一个例子来说明在严格模式下重新运行 Effects 如何帮助你及早发现错误。

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

🌐 Consider this example that connects a component to a chat:

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

import App from './App';

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

此代码存在问题,但可能不会立即清楚。

🌐 There is an issue with this code, but it might not be immediately clear.

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

🌐 To make the issue more obvious, let’s implement a feature. In the example below, roomId is not hardcoded. Instead, the user can select the roomId that they want to connect to from a dropdown. Click “Open chat” and then select different chat rooms one by one. Keep track of the number of active connections in the console:

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

import App from './App';

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

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

🌐 You’ll notice that the number of open connections always keeps growing. In a real app, this would cause performance and network problems. The issue is that your Effect is missing a cleanup function:

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

现在你的效果在执行后会自行“清理”并销毁过时的连接,泄漏问题得到解决。然而,请注意,问题直到你添加更多功能(选择框)后才变得明显。

🌐 Now that your Effect “cleans up” after itself and destroys the outdated connections, the leak is solved. However, notice that the problem did not become visible until you’ve added more features (the select box).

在原始示例中,这个错误并不明显。现在让我们把原始(有错误的)代码封装在 <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 时,你修复了许多可能的未来生产环境错误,就像之前的下拉框问题一样:

🌐 Strict Mode lets you notice such mistakes early in the process. When you fix your Effect by adding a cleanup function in Strict Mode, you also fix many possible future production bugs like the select box from before:

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

请注意控制台中的活动连接数如何不再继续增长。

🌐 Notice how the active connection count in the console doesn’t keep growing anymore.

没有严格模式时,很容易忽略你的 Effect 需要清理。通过在开发中为你的 Effect 运行 setup → cleanup → setup 而不是单独运行 setup,严格模式使得缺失的清理逻辑更明显。

🌐 Without Strict Mode, it was easy to miss that your Effect needed cleanup. By running setup → cleanup → setup instead of setup for your Effect in development, Strict Mode made the missing cleanup logic more noticeable.

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


修复在开发中重新运行引用回调发现的错误

🌐 Fixing bugs found by re-running ref callbacks in development

严格模式也可以帮助发现 回调引用 中的错误。

🌐 Strict Mode can also help find bugs in callbacks refs.

每个回调 ref 都有一些初始化代码,可能还有一些清理代码。通常,React 会在元素创建(被添加到 DOM)时调用初始化代码,在元素移除(从 DOM 中移除)时调用清理代码。

🌐 Every callback ref has some setup code and may have some cleanup code. Normally, React calls setup when the element is created (is added to the DOM) and calls cleanup when the element is removed (is removed from the DOM).

当严格模式开启时,React 在开发环境中也会对每个回调 ref 运行一个额外的设置+清理周期。这可能会让人感到意外,但它有助于揭示那些难以手动捕捉的微妙错误。

🌐 When Strict Mode is on, React will also run one extra setup+cleanup cycle in development for every callback ref. This may feel surprising, but it helps reveal subtle bugs that are hard to catch manually.

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

🌐 Consider this example, which allows you to select an animal and then scroll to one of them. Notice when you switch from “Cats” to “Dogs”, the console logs show that the number of animals in the list keeps growing, and the “Scroll to” buttons stop working:

import { useRef, useState } from "react";

export default function CatFriends() {
  const itemsRef = useRef([]);
  const [catList, setCatList] = useState(setupCatList);
  const [cat, setCat] = useState('neo');

  function scrollToCat(index) {
    const list = itemsRef.current;
    const {node} = list[index];
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }

  const cats = catList.filter(c => c.type === cat)

  return (
    <>
      <nav>
        <button onClick={() => setCat('neo')}>Neo</button>
        <button onClick={() => setCat('millie')}>Millie</button>
      </nav>
      <hr />
      <nav>
        <span>Scroll to:</span>{cats.map((cat, index) => (
          <button key={cat.src} onClick={() => scrollToCat(index)}>
            {index}
          </button>
        ))}
      </nav>
      <div>
        <ul>
          {cats.map((cat) => (
            <li
              key={cat.src}
              ref={(node) => {
                const list = itemsRef.current;
                const item = {cat: cat, node};
                list.push(item);
                console.log(`✅ Adding cat to the map. Total cats: ${list.length}`);
                if (list.length > 10) {
                  console.log('❌ Too many cats in the list!');
                }
                return () => {
                  // 🚩 No cleanup, this is a bug!
                }
              }}
            >
              <img src={cat.src} />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

function setupCatList() {
  const catList = [];
  for (let i = 0; i < 10; i++) {
    catList.push({type: 'neo', src: "https://placecats.com/neo/320/240?" + i});
  }
  for (let i = 0; i < 10; i++) {
    catList.push({type: 'millie', src: "https://placecats.com/millie/320/240?" + i});
  }

  return catList;
}

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

问题是引用回调不会自行清理:

🌐 The issue is the ref callback doesn’t cleanup after itself:

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

现在让我们将原始(有错误的)代码用 <StrictMode> 包起来:

🌐 Now let’s wrap the original (buggy) code in <StrictMode>:

import { useRef, useState } from "react";

export default function CatFriends() {
  const itemsRef = useRef([]);
  const [catList, setCatList] = useState(setupCatList);
  const [cat, setCat] = useState('neo');

  function scrollToCat(index) {
    const list = itemsRef.current;
    const {node} = list[index];
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }

  const cats = catList.filter(c => c.type === cat)

  return (
    <>
      <nav>
        <button onClick={() => setCat('neo')}>Neo</button>
        <button onClick={() => setCat('millie')}>Millie</button>
      </nav>
      <hr />
      <nav>
        <span>Scroll to:</span>{cats.map((cat, index) => (
          <button key={cat.src} onClick={() => scrollToCat(index)}>
            {index}
          </button>
        ))}
      </nav>
      <div>
        <ul>
          {cats.map((cat) => (
            <li
              key={cat.src}
              ref={(node) => {
                const list = itemsRef.current;
                const item = {cat: cat, node};
                list.push(item);
                console.log(`✅ Adding cat to the map. Total cats: ${list.length}`);
                if (list.length > 10) {
                  console.log('❌ Too many cats in the list!');
                }
                return () => {
                  // 🚩 No cleanup, this is a bug!
                }
              }}
            >
              <img src={cat.src} />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

function setupCatList() {
  const catList = [];
  for (let i = 0; i < 10; i++) {
    catList.push({type: 'neo', src: "https://placecats.com/neo/320/240?" + i});
  }
  for (let i = 0; i < 10; i++) {
    catList.push({type: 'millie', src: "https://placecats.com/millie/320/240?" + i});
  }

  return catList;
}

在严格模式下,你会立刻看到有问题。严格模式会对每个回调引用运行额外的设置+清理周期。这个回调引用没有清理逻辑,所以它会添加引用但不会移除它们。这是一个提示,表明你缺少清理函数。

严格模式让你能够积极地发现回调引用中的错误。当你在严格模式下通过添加清理函数来修复回调时,你也会修复许多可能的未来生产环境错误,例如之前的“滚动到”错误:

🌐 Strict Mode lets you eagerly find mistakes in callback refs. When you fix your callback by adding a cleanup function in Strict Mode, you also fix many possible future production bugs like the “Scroll to” bug from before:

import { useRef, useState } from "react";

export default function CatFriends() {
  const itemsRef = useRef([]);
  const [catList, setCatList] = useState(setupCatList);
  const [cat, setCat] = useState('neo');

  function scrollToCat(index) {
    const list = itemsRef.current;
    const {node} = list[index];
    node.scrollIntoView({
      behavior: "smooth",
      block: "nearest",
      inline: "center",
    });
  }

  const cats = catList.filter(c => c.type === cat)

  return (
    <>
      <nav>
        <button onClick={() => setCat('neo')}>Neo</button>
        <button onClick={() => setCat('millie')}>Millie</button>
      </nav>
      <hr />
      <nav>
        <span>Scroll to:</span>{cats.map((cat, index) => (
          <button key={cat.src} onClick={() => scrollToCat(index)}>
            {index}
          </button>
        ))}
      </nav>
      <div>
        <ul>
          {cats.map((cat) => (
            <li
              key={cat.src}
              ref={(node) => {
                const list = itemsRef.current;
                const item = {cat: cat, node};
                list.push(item);
                console.log(`✅ Adding cat to the map. Total cats: ${list.length}`);
                if (list.length > 10) {
                  console.log('❌ Too many cats in the list!');
                }
                return () => {
                  list.splice(list.indexOf(item), 1);
                  console.log(`❌ Removing cat from the map. Total cats: ${itemsRef.current.length}`);
                }
              }}
            >
              <img src={cat.src} />
            </li>
          ))}
        </ul>
      </div>
    </>
  );
}

function setupCatList() {
  const catList = [];
  for (let i = 0; i < 10; i++) {
    catList.push({type: 'neo', src: "https://placecats.com/neo/320/240?" + i});
  }
  for (let i = 0; i < 10; i++) {
    catList.push({type: 'millie', src: "https://placecats.com/millie/320/240?" + i});
  }

  return catList;
}

现在在 StrictMode 中的初始挂载上,ref 回调全部设置、清理并再次设置:

🌐 Now on inital mount in StrictMode, the ref callbacks are all setup, cleaned up, and setup again:

...
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 回调被正确清理,因此大小永远不会超过预期值。修复后,没有内存泄漏,所有功能都按预期工作。

没有严格模式时,你很容易遗漏错误,直到你点击应用时才注意到功能损坏。严格模式会立即显示错误,在你将它们推送到生产环境之前。

🌐 Without Strict Mode, it was easy to miss the bug until you clicked around to app to notice broken features. Strict Mode made the bugs appear right away, before you push them to production.


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

🌐 Fixing deprecation warnings enabled by Strict Mode

如果 <StrictMode> 树中的某个组件使用了这些已弃用的 API,React 会发出警告:

🌐 React warns if some component anywhere inside a <StrictMode> tree uses one of these deprecated APIs:

这些 API 主要用于较旧的 类组件,所以在现代应用中很少出现。

🌐 These APIs are primarily used in older class components so they rarely appear in modern apps.