<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` 元件接收一個 `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` 直接修改了來自屬性的 `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>` 中。

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

嚴格模式 (Strict Mode) *永遠* 會呼叫您的渲染函式兩次,以便您立即發現錯誤(「建立故事」會出現兩次)。這讓您可以在流程早期就注意到此類錯誤。當您修復組件使其在嚴格模式下正確渲染時,您*也*修復了許多未來可能發生的生產環境錯誤,例如之前的懸停功能問題。

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

如果沒有嚴格模式,在您新增更多重新渲染之前,很容易錯過這個錯誤。嚴格模式會讓相同的錯誤立即出現。嚴格模式可幫助您在將錯誤推送給團隊和使用者之前就找到它們。

閱讀更多關於保持組件純粹性的資訊。

注意事項

如果您已安裝 React 開發者工具,則在第二次渲染呼叫期間的任何 console.log 呼叫會稍微變暗。React 開發者工具也提供了一個設定(預設關閉)來完全抑制它們。


修復在開發過程中重新執行 Effects 所發現的錯誤

嚴格模式也可以幫助找出 Effects 中的錯誤。

每個 Effect 都有一些設定程式碼,也可能有一些清除程式碼。通常,React 在組件*掛載*(新增到螢幕)時呼叫設定,並在組件*卸載*(從螢幕移除)時呼叫清除。如果 Effects 的依賴項自上次渲染以來發生變化,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 會自行「清理」並銷毀過時的連線,洩漏問題已解決。但是,請注意,在您新增更多功能(選擇框)之前,問題並不明顯。

在原始範例中,錯誤並不十分明顯。現在,讓我們將原始(錯誤的)程式碼包裹在 `<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 回呼所發現的錯誤

嚴格模式也可以幫助找出回呼 ref 中的錯誤。

每個回呼 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;
}

這是一個生產環境錯誤!由於 ref 回呼沒有在清除時從列表中移除動物,因此動物列表持續增長。這是一個記憶體洩漏,會在實際應用程式中導致效能問題,並破壞應用程式的行為。

問題是 ref 回呼沒有自行清理。

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

現在讓我們將原始(錯誤的)程式碼包裝在 <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 中的錯誤。當您透過在嚴格模式下新增清除函式來修復回呼時,您*也*修復了許多未來可能發生的生產環境錯誤,例如之前的「滾動到」錯誤。

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 回呼已正確清除,因此大小永遠不會超過預期數量。修復後,沒有記憶體洩漏,所有功能都按預期工作。

如果沒有嚴格模式,在您點擊應用程式並注意到功能損壞之前,很容易錯過這個錯誤。嚴格模式會讓錯誤立即出現,在您將它們推送至生產環境之前。


修復由嚴格模式啟用的棄用警告

如果 <StrictMode> 樹狀結構中的任何組件使用其中一個已棄用的 API,React 就會發出警告。

這些 API 主要用於較舊的 類別組件 中,因此它們很少出現在現代應用程式中。