<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>
);
厳格モードでは、開発専用の次の動作が有効になります
- コンポーネントが、純粋でないレンダリングによって発生するバグを見つけるために、余分に再レンダリングされます。
- コンポーネントが、Effectのクリーンアップの欠落によって発生するバグを見つけるために、Effectを余分に再実行します。
- コンポーネントが、refクリーンアップの欠落によって発生するバグを見つけるために、refコールバックを余分に再実行します。
- コンポーネントが、非推奨のAPIの使用状況についてチェックされます。
Props
StrictMode
はpropsを受け入れません。
注意点
<StrictMode>
でラップされたツリー内で厳格モードをオプトアウトする方法はありません。これにより、<StrictMode>
内のすべてのコンポーネントがチェックされるという確信が得られます。製品に取り組んでいる2つのチームが、チェックが価値があるかどうかについて意見が合わない場合は、合意に達するか、ツリー内で<StrictMode>
を下に移動する必要があります。
使い方
アプリ全体で Strict Mode を有効にする
Strict Mode は、<StrictMode>
コンポーネント内のコンポーネントツリー全体に対して、開発時のみの追加チェックを有効にします。これらのチェックは、開発プロセスの早い段階でコンポーネント内の一般的なバグを見つけるのに役立ちます。
アプリ全体で Strict Mode を有効にするには、ルートコンポーネントをレンダリングする際に、<StrictMode>
でラップしてください。
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
特に新しく作成されたアプリでは、アプリ全体を Strict Mode でラップすることをお勧めします。createRoot
を呼び出すフレームワークを使用している場合は、Strict Mode を有効にする方法についてドキュメントを確認してください。
Strict Mode のチェックは開発時のみ実行されますが、コードに既に存在していて、本番環境で確実に再現するのが難しいバグを見つけるのに役立ちます。Strict Mode を使用すると、ユーザーが報告する前にバグを修正できます。
アプリの一部で Strict Mode を有効にする
アプリケーションの一部に対して Strict Mode を有効にすることもできます。
import { StrictMode } from 'react';
function App() {
return (
<>
<Header />
<StrictMode>
<main>
<Sidebar />
<Content />
</main>
</StrictMode>
<Footer />
</>
);
}
この例では、Header
および Footer
コンポーネントに対して Strict Mode チェックは実行されません。ただし、Sidebar
および Content
、およびその内部のすべてのコンポーネント (深さに関係なく) で実行されます。
開発時の二重レンダリングで見つかったバグの修正
React は、記述するすべてのコンポーネントが純粋な関数であると仮定します。つまり、記述する React コンポーネントは、同じ入力 (props、state、および context) が与えられた場合、常に同じ JSX を返す必要があります。
このルールを破るコンポーネントは、予測不能な動作をし、バグを引き起こします。誤って純粋でないコードを見つけるのに役立つように、Strict Mode は一部の関数 (純粋である必要があるもののみ) を開発時に 2 回呼び出します。これには以下が含まれます。
- コンポーネントの関数本体 (トップレベルのロジックのみであるため、イベントハンドラー内のコードは含まれません)
useState
、set
関数、useMemo
、またはuseReducer
に渡す関数。constructor
、render
、shouldComponentUpdate
などのクラスコンポーネントメソッド (リスト全体を参照)
関数が純粋な場合、純粋な関数は常に同じ結果を生成するため、2 回実行しても動作は変わりません。ただし、関数が純粋でない場合 (たとえば、受信したデータを変更する場合)、2 回実行すると目立つ傾向があります (それが純粋でない理由です!)。これにより、バグを早期に特定して修正することができます。
Strict Mode での二重レンダリングが、どのようにバグを早期に発見するのに役立つかを示す例を次に示します。
この 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
コンポーネントにカーソルを合わせるたびに、「ストーリーを作成」がリストに再び追加されていることに注目してください。コードの意図は、最後に 1 回追加することでした。しかし、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
関数が純粋になります。呼び出されるたびに、配列の新しいコピーのみを変更し、外部のオブジェクトや変数には影響しません。これによりバグが解決されますが、コンポーネントをより頻繁に再レンダリングしないと、その動作に問題があることが明らかになりませんでした。
元の例では、バグは明らかではありませんでした。ここで、元の (バグのある) コードを <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 は、レンダリング関数を必ず 2 回呼び出すため、すぐに間違いに気づくことができます(「Create Story」が 2 回表示されます)。これにより、開発プロセスの早い段階でこのような間違いに気づくことができます。コンポーネントを修正して 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> ); }
Strict Mode がない場合、より多くの再レンダリングを追加するまでバグを見逃しがちでした。Strict Mode を使用すると、同じバグがすぐに表示されます。Strict Mode は、チームやユーザーにバグを公開する前に、バグを見つけるのに役立ちます。
コンポーネントを純粋に保つ方法について詳しくはこちらをご覧ください。
開発環境で Effect を再実行して見つかったバグの修正
Strict Mode は、Effect のバグを見つけるのにも役立ちます。
すべての Effect にはセットアップコードがあり、クリーンアップコードがある場合があります。通常、React は、コンポーネントがマウントされたとき(画面に追加されたとき)にセットアップを呼び出し、コンポーネントがアンマウントされたとき(画面から削除されたとき)にクリーンアップを呼び出します。その後、依存関係が最後のレンダリングから変更された場合、React はクリーンアップとセットアップを再度呼び出します。
Strict Mode がオンの場合、React は開発環境で すべての Effect に対して追加のセットアップ + クリーンアップサイクルを 1 回実行します。 これは驚くべきことかもしれませんが、手動で検出するのが難しい微妙なバグを明らかにするのに役立ちます。
Strict Mode で Effect を再実行することが、早期にバグを見つけるのにどのように役立つかを示す例を次に示します。
コンポーネントをチャットに接続する次の例を考えてみましょう。
import { createRoot } from 'react-dom/client'; import './styles.css'; import App from './App'; const root = createRoot(document.getElementById("root")); root.render(<App />);
このコードには問題がありますが、すぐには明らかにならないかもしれません。
問題をより明確にするために、機能を実装してみましょう。下の例では、roomId
はハードコードされていません。代わりに、ユーザーはドロップダウンから接続する roomId
を選択できます。「チャットを開く」をクリックして、チャットルームを 1 つずつ選択します。コンソールでアクティブな接続の数を追跡してください。
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> );
Strict Mode を使用すると、すぐに問題があることがわかります(アクティブな接続の数が 2 にジャンプします)。Strict Mode は、すべての Effect に対して追加のセットアップ + クリーンアップサイクルを実行します。この Effect にはクリーンアップロジックがないため、追加の接続が作成されますが、破棄されません。これは、クリーンアップ関数が欠落していることを示唆しています。
Strict Mode を使用すると、プロセスの初期段階でこのような間違いに気づくことができます。Strict Mode でクリーンアップ関数を追加して 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> );
コンソールのアクティブな接続数が、それ以上増えなくなったことに注意してください。
Strict Mode がない場合、Effect にクリーンアップが必要であることを見逃しがちでした。開発環境で Effect に対して、セットアップの代わりに *セットアップ → クリーンアップ → セットアップ* を実行することで、Strict Mode は欠落しているクリーンアップロジックをより顕著にしました。
Effect のクリーンアップの実装について詳しくはこちらをご覧ください。
開発環境で ref コールバックを再実行して見つかったバグの修正
Strict Mode は、コールバック ref のバグを見つけるのにも役立ちます。
すべてのコールバック ref
にはセットアップコードがあり、クリーンアップコードがある場合があります。通常、React は、要素が作成されたとき(DOM に追加されたとき)にセットアップを呼び出し、要素が削除されたとき(DOM から削除されたとき)にクリーンアップを呼び出します。
Strict Mode がオンの場合、React は開発環境で すべてのコールバック ref
に対して追加のセットアップ + クリーンアップサイクルを 1 回実行します。 これは驚くべきことかもしれませんが、手動で検出するのが難しい微妙なバグを明らかにするのに役立ちます。
動物を選択して、その動物にスクロールできるようにする次の例を考えてみましょう。「猫」から「犬」に切り替えると、コンソールログにリスト内の動物の数が増え続けており、「スクロール先」ボタンが機能しなくなることに注意してください。
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; }
Strict Mode を使用すると、すぐに問題があることがわかります。Strict Mode は、すべてのコールバック ref に対して追加のセットアップ + クリーンアップサイクルを実行します。このコールバック ref にはクリーンアップロジックがないため、ref を追加しますが、削除しません。これは、クリーンアップ関数が欠落していることを示唆しています。
Strict Mode を使用すると、コールバック ref の間違いを早期に発見できます。Strict Mode でクリーンアップ関数を追加してコールバックを修正すると、以前の「スクロール先」バグのように、将来発生する可能性のある多くの本番環境のバグも同時に修正できます。
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; }
Strict Mode での最初のマウント時に、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
これは想定どおりです。 Strict Mode は、ref コールバックが正しくクリーンアップされていることを確認するため、サイズが想定量を超えることはありません。修正後、メモリリークはなくなり、すべての機能が期待どおりに動作します。
Strict Mode がない場合、アプリをクリックして壊れた機能に気づくまで、バグを見逃しがちでした。Strict Mode は、本番環境にプッシュする前に、バグをすぐに表示させました。
Strict Mode で有効になる非推奨警告の修正
<StrictMode>
ツリー内のいずれかのコンポーネントが、これらの非推奨 API のいずれかを使用している場合、React は警告を発します。
UNSAFE_
のようなクラスのライフサイクルメソッドは、UNSAFE_componentWillMount
などがあります。代替手段を参照してください。
これらのAPIは主に古いクラスコンポーネントで使用されるため、最近のアプリではほとんど見られません。