React StrictModeとは?開発時の問題を早期発見する方法

React StrictModeの基本概念から実践的な使い方まで詳しく解説。開発時の問題検出、パフォーマンス向上、将来のReactバージョンへの対応方法を紹介します。

Learning Next 運営
46 分で読めます

React StrictModeとは?開発時の問題を早期発見する方法

みなさん、React開発で「本番環境でだけ問題が発生する」という経験はありませんか?

「開発中に潜在的な問題を発見したい...」 「将来のReactバージョンに対応したコードを書きたい...」 こんな悩みを持ったことがある方は多いのではないでしょうか?

実は、こうした問題を解決してくれる強力なツールがあるんです。 それがReact StrictModeです!

この記事では、React StrictModeの基本概念から実践的な使い方まで詳しく解説します。 開発時の問題検出、パフォーマンス向上、将来のReactバージョンへの対応方法を具体的なコードとともに学んでいきましょう。

React StrictModeって何?基本を理解しよう

StrictModeの役割

React StrictModeは、開発時にアプリケーションの潜在的な問題を検出するためのツールです。

簡単に言うと、コードの「健康診断」をしてくれる機能なんです。 本番環境では何も影響しませんが、開発中に様々な問題を教えてくれます。

StrictModeが検出する問題の例

まず、問題のあるコードを見てみましょう。

// StrictModeが検出する問題の例
const ProblematicComponent = () => {
  const [count, setCount] = useState(0);
  
  // 🚨 問題:副作用がuseEffectの外で実行されている
  console.log('レンダリング中にログ出力'); // StrictModeで2回実行される
  
  // 🚨 問題:非推奨のAPIを使用
  const ref = useRef();
  
  useEffect(() => {
    // 🚨 問題:クリーンアップ関数がない副作用
    const timer = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    // return () => clearInterval(timer); // これが必要
  }, []);
  
  return <div>Count: {count}</div>;
};

上記のコードは一見問題なく動きそうですが、実は複数の問題があります。 StrictModeは、これらの問題を開発時に警告として教えてくれるんです。

主な問題点

  • レンダリング中の副作用(console.log)
  • クリーンアップ関数の欠如
  • メモリリークの可能性

StrictModeの基本的な使い方

StrictModeの使い方はとてもシンプルです。

import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';

const App = () => {
  return (
    <div>
      <h1>メインアプリケーション</h1>
      <MainContent />
    </div>
  );
};

// StrictModeの適用方法
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

たったこれだけで、アプリ全体がStrictModeの監視下に入ります!

StrictModeの主な特徴

  • 開発環境のみ動作: 本番環境では何も行わない
  • コンポーネントの2回実行: 副作用の問題を検出
  • useEffectの2回実行: クリーンアップをテスト
  • 非推奨APIの警告: 古いAPIの使用を警告

安心してください。 本番環境では何の影響もありませんので、気軽に使えます!

StrictModeが具体的に検出してくれる問題

もう少し詳しく、どんな問題を検出してくれるのか見てみましょう。

副作用の問題

// ❌ 問題のあるコンポーネント
const BadComponent = () => {
  const [data, setData] = useState(null);
  
  // 副作用がレンダリング中に実行されている
  fetch('/api/data').then(response => {
    response.json().then(setData); // StrictModeで2回実行される
  });
  
  return <div>{data ? data.message : 'Loading...'}</div>;
};

上記のコードは、レンダリングのたびにAPIを呼び出してしまいます。 StrictModeでは、これが2回実行されることで問題が明確になります。

// ✅ 修正されたコンポーネント
const GoodComponent = () => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        const response = await fetch('/api/data');
        const result = await response.json();
        
        if (!cancelled) {
          setData(result);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          console.error('データ取得エラー:', error);
          setLoading(false);
        }
      }
    };
    
    fetchData();
    
    // クリーンアップ関数
    return () => {
      cancelled = true;
    };
  }, []);
  
  if (loading) return <div>Loading...</div>;
  return <div>{data ? data.message : 'No data'}</div>;
};

修正版では、以下の改善が行われています。

改善点

  • useEffect内でAPI呼び出し: 適切なタイミングで実行
  • クリーンアップ関数: メモリリークを防止
  • キャンセル機能: 不要なリクエストを防止
  • エラーハンドリング: 適切な例外処理

これで、StrictModeで検出される問題がすべて解決されました!

StrictModeの導入と設定

基本的な導入方法

StrictModeをプロジェクトに導入する方法はいくつかあります。 最も一般的で推奨される方法から見てみましょう。

アプリケーション全体への適用

// src/index.js - アプリケーション全体にStrictModeを適用
import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './index.css';

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

この方法では、App以下のすべてのコンポーネントがStrictModeの対象になります。 最も一般的で推奨される方法ですね。

メリット

  • シンプルな設定
  • アプリ全体を一括チェック
  • 設定漏れがない

部分的な適用

特定のコンポーネントツリーのみにStrictModeを適用することもできます。

// 特定のコンポーネントツリーのみにStrictModeを適用
import React, { StrictMode } from 'react';

const App = () => {
  return (
    <div>
      {/* ヘッダーは通常モード */}
      <Header />
      
      {/* メインコンテンツのみStrictMode */}
      <StrictMode>
        <MainContent />
        <Sidebar />
      </StrictMode>
      
      {/* フッターは通常モード */}
      <Footer />
    </div>
  );
};

この方法は段階的な導入や、問題の特定に便利です。

使用場面

  • 段階的な導入
  • 問題のあるコンポーネントの切り分け
  • パフォーマンステスト

環境による条件付き適用

開発環境でのみStrictModeを有効にすることもできます。

// 環境変数による条件付きStrictMode
import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));

const AppWithConditionalStrictMode = () => {
  // 開発環境でのみStrictModeを有効化
  if (process.env.NODE_ENV === 'development') {
    return (
      <StrictMode>
        <App />
      </StrictMode>
    );
  }
  
  return <App />;
};

root.render(<AppWithConditionalStrictMode />);

より細かい制御も可能です。

// より細かい制御が可能
const AdvancedStrictModeWrapper = ({ children }) => {
  const shouldUseStrictMode = 
    process.env.NODE_ENV === 'development' &&
    process.env.REACT_APP_STRICT_MODE !== 'false';
  
  if (shouldUseStrictMode) {
    return <StrictMode>{children}</StrictMode>;
  }
  
  return children;
};

環境変数を使うことで、チーム全体で一貫した設定を管理できます。

開発ツールとの連携

StrictModeを他の開発ツールと組み合わせることで、より効果的に使えます。

エラー境界との組み合わせ

// StrictModeとError Boundaryの組み合わせ
import React, { StrictMode, Component } from 'react';

class StrictModeErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // StrictModeで発生するエラーを詳細にログ
    console.group('🚨 StrictMode Error Boundary');
    console.error('Error:', error);
    console.error('Error Info:', errorInfo);
    console.error('Component Stack:', errorInfo.componentStack);
    
    // 開発時の詳細な情報
    if (process.env.NODE_ENV === 'development') {
      console.warn('💡 StrictModeにより、この問題が早期発見されました');
      console.warn('🔧 本番環境では発生しない可能性があります');
    }
    
    console.groupEnd();
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div style={{
          padding: '20px',
          border: '2px solid #dc3545',
          borderRadius: '8px',
          backgroundColor: '#f8d7da',
          color: '#721c24'
        }}>
          <h2>🚨 開発時エラーが検出されました</h2>
          <p>StrictModeによって潜在的な問題が発見されました。</p>
          <details>
            <summary>エラー詳細</summary>
            <pre style={{ fontSize: '12px', overflow: 'auto' }}>
              {this.state.error?.toString()}
            </pre>
          </details>
          <button 
            onClick={() => this.setState({ hasError: false, error: null })}
            style={{
              marginTop: '10px',
              padding: '8px 16px',
              backgroundColor: '#dc3545',
              color: 'white',
              border: 'none',
              borderRadius: '4px',
              cursor: 'pointer'
            }}
          >
            リトライ
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// 使用例
const AppWithStrictModeAndErrorBoundary = () => {
  return (
    <StrictMode>
      <StrictModeErrorBoundary>
        <App />
      </StrictModeErrorBoundary>
    </StrictMode>
  );
};

このように組み合わせることで、エラーの詳細な分析と対処が可能になります。

組み合わせのメリット

  • エラーの詳細ログ取得
  • 開発時の問題の可視化
  • ユーザーフレンドリーなエラー表示
  • 問題の早期発見

StrictModeで検出される具体的な問題例

useEffectの問題検出

StrictModeがuseEffectの問題をどのように検出するか、詳しく見てみましょう。

データフェッチングの問題

よくある問題のあるデータフェッチングから見てみましょう。

// ❌ 問題のあるデータフェッチング
const BadDataFetching = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // StrictModeで2回実行される
    console.log('Effect実行:', userId);
    
    fetch(`/api/users/${userId}`)
      .then(response => response.json())
      .then(userData => {
        setUser(userData);
        setLoading(false);
      })
      .catch(error => {
        console.error('Error:', error);
        setLoading(false);
      });
    
    // クリーンアップがないため、コンポーネントが再マウントされると
    // 古いリクエストが残り続ける可能性がある
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  return <div>User: {user?.name}</div>;
};

この問題のあるコードでは、以下の問題があります。

主な問題点

  • クリーンアップ不足: 古いリクエストが残る
  • メモリリーク: コンポーネント削除後もリクエストが続く
  • 競合状態: 複数のリクエストが同時実行される

次に、修正されたバージョンを見てみましょう。

// ✅ 修正されたデータフェッチング
const GoodDataFetching = ({ userId }) => {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    // キャンセル用のAbortController
    const abortController = new AbortController();
    
    const fetchUser = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch(`/api/users/${userId}`, {
          signal: abortController.signal
        });
        
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const userData = await response.json();
        setUser(userData);
      } catch (err) {
        // キャンセルされた場合は無視
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
    
    // クリーンアップ関数でリクエストをキャンセル
    return () => {
      abortController.abort();
    };
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>User: {user?.name}</div>;
};

修正版の改善点を説明しますね。

改善点

  • AbortController: リクエストのキャンセル機能
  • 適切なエラーハンドリング: 例外処理の改善
  • 競合状態の防止: 古いリクエストのキャンセル
  • メモリリーク防止: クリーンアップの実装

これで、StrictModeで安全に動作するコンポーネントになりました!

タイマーとインターバルの問題

タイマー処理でもよくある問題があります。

// ❌ 問題のあるタイマー処理
const BadTimer = () => {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    // StrictModeで複数のタイマーが作成される
    const interval = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    // クリーンアップがない!
  }, []);
  
  return <div>Count: {count}</div>;
};

このコードの問題は、クリーンアップ関数がないことです。 StrictModeでは、これにより複数のタイマーが作成されてしまいます。

// ✅ 修正されたタイマー処理
const GoodTimer = () => {
  const [count, setCount] = useState(0);
  const [isRunning, setIsRunning] = useState(true);
  
  useEffect(() => {
    if (!isRunning) return;
    
    const interval = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);
    
    // 適切なクリーンアップ
    return () => {
      clearInterval(interval);
    };
  }, [isRunning]);
  
  const toggleTimer = () => {
    setIsRunning(prev => !prev);
  };
  
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={toggleTimer}>
        {isRunning ? '停止' : '開始'}
      </button>
    </div>
  );
};

修正のポイント

  • clearInterval: タイマーのクリーンアップ
  • 条件付き実行: isRunningによる制御
  • ユーザー制御: 開始/停止ボタンの追加

これで、メモリリークを防ぎ、ユーザーが制御できるタイマーになりました。

状態管理の問題検出

複雑な状態管理での問題とその解決方法も見てみましょう。

状態更新の競合問題

// ❌ 問題のある状態管理
const BadComplexState = () => {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');
  const [editingId, setEditingId] = useState(null);
  
  // レンダリング中に副作用を実行している
  const filteredTodos = todos.filter(todo => {
    // この中でAPIコールなどの副作用を行うと問題
    if (filter === 'all') return true;
    if (filter === 'active') return !todo.completed;
    if (filter === 'completed') return todo.completed;
    return true;
  });
  
  const addTodo = (text) => {
    // 状態の依存関係が複雑
    setTodos(prev => [...prev, {
      id: Date.now(),
      text,
      completed: false
    }]);
    
    // 複数の状態を同時に更新(競合状態の可能性)
    setEditingId(null);
    setFilter('all');
  };
  
  return (
    <div>
      {/* コンポーネントの実装 */}
    </div>
  );
};

この状態管理には、いくつかの問題があります。

主な問題

  • 副作用の混在: フィルタリング中の副作用
  • 競合状態: 複数の状態更新
  • 依存関係の複雑化: 状態間の依存

修正版を見てみましょう。

// ✅ 改善された状態管理
const GoodComplexState = () => {
  const [todos, setTodos] = useState([]);
  const [filter, setFilter] = useState('all');
  const [editingId, setEditingId] = useState(null);
  
  // メモ化による最適化
  const filteredTodos = useMemo(() => {
    return todos.filter(todo => {
      if (filter === 'all') return true;
      if (filter === 'active') return !todo.completed;
      if (filter === 'completed') return todo.completed;
      return true;
    });
  }, [todos, filter]);
  
  // 状態更新の統合
  const addTodo = useCallback((text) => {
    setTodos(prev => [...prev, {
      id: Date.now(),
      text,
      completed: false
    }]);
    
    // バッチ更新の活用
    React.unstable_batchedUpdates(() => {
      setEditingId(null);
      setFilter('all');
    });
  }, []);
  
  const toggleTodo = useCallback((id) => {
    setTodos(prev => 
      prev.map(todo => 
        todo.id === id 
          ? { ...todo, completed: !todo.completed }
          : todo
      )
    );
  }, []);
  
  return (
    <div>
      <div>
        {filteredTodos.map(todo => (
          <div key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.text}</span>
          </div>
        ))}
      </div>
      
      <div>
        <button onClick={() => setFilter('all')}>All</button>
        <button onClick={() => setFilter('active')}>Active</button>
        <button onClick={() => setFilter('completed')}>Completed</button>
      </div>
    </div>
  );
};

改善ポイント

  • useMemo: フィルタリングの最適化
  • useCallback: 関数の最適化
  • バッチ更新: 複数状態の一括更新
  • 純粋な関数: 副作用の分離

適切な状態管理により、パフォーマンスと保守性が向上しました!

パフォーマンスへの影響と対策

StrictModeのパフォーマンス影響

StrictModeが開発時のパフォーマンスに与える影響を理解しておきましょう。

二重実行による影響

StrictModeでは、コンポーネントやEffectが意図的に2回実行されます。 これがパフォーマンスに与える影響を測定してみましょう。

// StrictModeでの実行回数を測定
const PerformanceMonitor = () => {
  const [renderCount, setRenderCount] = useState(0);
  const [effectCount, setEffectCount] = useState(0);
  const renderCountRef = useRef(0);
  
  // レンダリング回数のカウント
  renderCountRef.current += 1;
  
  useEffect(() => {
    // Effect実行回数のカウント
    setEffectCount(prev => prev + 1);
    
    console.log(`Render: ${renderCountRef.current}, Effect: ${effectCount + 1}`);
    
    // パフォーマンス測定
    const start = performance.now();
    
    return () => {
      const end = performance.now();
      console.log(`Effect duration: ${end - start}ms`);
    };
  });
  
  useEffect(() => {
    setRenderCount(renderCountRef.current);
  });
  
  return (
    <div style={{
      padding: '20px',
      border: '1px solid #ccc',
      borderRadius: '8px',
      backgroundColor: '#f9f9f9'
    }}>
      <h3>パフォーマンス監視</h3>
      <p>レンダリング回数: {renderCount}</p>
      <p>Effect実行回数: {effectCount}</p>
      <p style={{ fontSize: '12px', color: '#666' }}>
        StrictModeでは値が2倍になります
      </p>
    </div>
  );
};

このコンポーネントを使うことで、開発時のパフォーマンス影響を定量的に把握できます。

測定結果の活用方法

  • 実行回数の確認
  • パフォーマンスボトルネックの特定
  • 最適化の効果測定

重い処理の最適化

StrictModeで問題になりがちな重い処理の最適化方法を学びましょう。

// ❌ StrictModeで問題になる重い処理
const HeavyComponent = ({ data }) => {
  // レンダリングのたびに重い計算が実行される
  const expensiveValue = data.reduce((acc, item) => {
    // 重い計算処理
    return acc + item.value * Math.random();
  }, 0);
  
  useEffect(() => {
    // 重い副作用処理
    const result = performHeavyCalculation(data);
    console.log('Heavy calculation result:', result);
  }, [data]);
  
  return <div>Result: {expensiveValue}</div>;
};

この重い処理は、StrictModeで2倍の負荷がかかってしまいます。

// ✅ 最適化された処理
const OptimizedHeavyComponent = ({ data }) => {
  // useMemoで重い計算をメモ化
  const expensiveValue = useMemo(() => {
    console.log('重い計算を実行中...');
    return data.reduce((acc, item) => {
      return acc + item.value * Math.random();
    }, 0);
  }, [data]);
  
  // 重い副作用の最適化
  useEffect(() => {
    const abortController = new AbortController();
    
    const performCalculation = async () => {
      try {
        const result = await performHeavyCalculationAsync(data, {
          signal: abortController.signal
        });
        
        if (!abortController.signal.aborted) {
          console.log('Heavy calculation result:', result);
        }
      } catch (error) {
        if (error.name !== 'AbortError') {
          console.error('Calculation error:', error);
        }
      }
    };
    
    performCalculation();
    
    return () => {
      abortController.abort();
    };
  }, [data]);
  
  return <div>Result: {expensiveValue}</div>;
};

最適化のポイント

  • useMemo: 重い計算のメモ化
  • AbortController: 処理のキャンセル機能
  • 非同期処理: UIのブロッキング回避
  • エラーハンドリング: 適切な例外処理

これで、StrictModeでも快適な開発体験を維持できます!

開発環境での設定調整

チーム開発で一貫したStrictMode設定を管理する方法を学びましょう。

環境別の設定管理

// 環境設定の管理
const DevelopmentConfig = {
  strictMode: {
    enabled: process.env.REACT_APP_STRICT_MODE !== 'false',
    level: process.env.REACT_APP_STRICT_LEVEL || 'standard' // 'minimal', 'standard', 'aggressive'
  },
  
  performance: {
    enableProfiling: process.env.REACT_APP_PROFILING === 'true',
    logRenderTime: process.env.REACT_APP_LOG_RENDER_TIME === 'true',
    enableStrictModeWarnings: process.env.REACT_APP_STRICT_WARNINGS !== 'false'
  },
  
  debugging: {
    enableConsoleGroups: process.env.REACT_APP_CONSOLE_GROUPS === 'true',
    enableErrorBoundaryLogging: process.env.REACT_APP_ERROR_LOGGING !== 'false'
  }
};

// 設定に基づくコンポーネントラッパー
const DevelopmentWrapper = ({ children }) => {
  const shouldUseStrictMode = DevelopmentConfig.strictMode.enabled;
  const enableProfiling = DevelopmentConfig.performance.enableProfiling;
  
  let wrappedChildren = children;
  
  // StrictModeの適用
  if (shouldUseStrictMode) {
    wrappedChildren = <StrictMode>{wrappedChildren}</StrictMode>;
  }
  
  // Profilerの適用
  if (enableProfiling) {
    wrappedChildren = (
      <Profiler
        id="App"
        onRender={(id, phase, actualDuration, baseDuration, startTime, commitTime) => {
          console.log('Profiler:', {
            id,
            phase,
            actualDuration,
            baseDuration,
            startTime,
            commitTime
          });
        }}
      >
        {wrappedChildren}
      </Profiler>
    );
  }
  
  return wrappedChildren;
};

この設定システムにより、チーム全体で一貫した開発環境を維持できます。

設定ファイルの例(.env.development)

REACT_APP_STRICT_MODE=true
REACT_APP_STRICT_LEVEL=standard
REACT_APP_PROFILING=false
REACT_APP_LOG_RENDER_TIME=true
REACT_APP_STRICT_WARNINGS=true
REACT_APP_CONSOLE_GROUPS=true
REACT_APP_ERROR_LOGGING=true

環境変数を使うことで、開発者ごとの設定調整も可能です。

実践的なデバッグ手法

StrictModeでのデバッグワークフロー

効果的なデバッグ手法を身につけましょう。

問題の特定と修正プロセス

StrictModeで問題を発見したときの、系統的な対処法をお伝えします。

// デバッグ支援コンポーネント
const DebugHelper = ({ children, name }) => {
  const renderCount = useRef(0);
  const [debugInfo, setDebugInfo] = useState({});
  
  renderCount.current += 1;
  
  useEffect(() => {
    const startTime = performance.now();
    
    setDebugInfo(prev => ({
      ...prev,
      [`${name}_mount_time`]: startTime
    }));
    
    return () => {
      const endTime = performance.now();
      console.log(`🔧 [${name}] Mount duration: ${endTime - startTime}ms`);
    };
  }, [name]);
  
  // レンダリング情報のログ
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      console.group(`🔍 [${name}] Debug Info`);
      console.log(`Render count: ${renderCount.current}`);
      console.log(`Timestamp: ${new Date().toISOString()}`);
      console.log('Props:', children.props);
      console.groupEnd();
    }
  });
  
  return (
    <div data-debug-component={name}>
      {children}
    </div>
  );
};

// 使用例
const DebuggableComponent = ({ data }) => {
  return (
    <DebugHelper name="DebuggableComponent">
      <div>
        <h3>データ表示</h3>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    </DebugHelper>
  );
};

このデバッグヘルパーを使うことで、系統的な問題解析ができます。

デバッグ情報の活用

  • レンダリング回数の追跡
  • マウント時間の測定
  • プロパティの変化追跡
  • コンポーネントの識別

StrictMode特有の問題の対処法

StrictModeの特性を考慮したカスタムフックを作成しましょう。

// StrictMode対応のカスタムフック
const useStrictModeAwareEffect = (effect, deps) => {
  const effectRan = useRef(false);
  
  useEffect(() => {
    // StrictModeでの重複実行を防ぐ
    if (effectRan.current === false) {
      effectRan.current = true;
      return effect();
    }
  }, deps);
  
  // クリーンアップ時にフラグをリセット
  useEffect(() => {
    return () => {
      effectRan.current = false;
    };
  }, []);
};

// ログ出力の重複を防ぐフック
const useStrictModeAwareLog = (message, deps) => {
  const loggedRef = useRef(false);
  
  useEffect(() => {
    if (!loggedRef.current) {
      console.log(message);
      loggedRef.current = true;
    }
    
    return () => {
      loggedRef.current = false;
    };
  }, deps);
};

// API呼び出しの重複を防ぐフック
const useStrictModeAwareAPI = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const requestRef = useRef(null);
  
  useEffect(() => {
    // 既存のリクエストがある場合はキャンセル
    if (requestRef.current) {
      requestRef.current.abort();
    }
    
    const abortController = new AbortController();
    requestRef.current = abortController;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch(url, {
          signal: abortController.signal
        });
        
        const result = await response.json();
        setData(result);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err);
        }
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
    
    return () => {
      abortController.abort();
      requestRef.current = null;
    };
  }, [url]);
  
  return { data, loading, error };
};

これらのカスタムフックにより、StrictModeの特性を考慮した堅牢なコードが書けます。

カスタムフックの利点

  • StrictModeでの重複実行対策
  • 一貫したエラーハンドリング
  • 再利用可能なロジック
  • チーム全体での標準化

チーム開発での活用

チーム全体でStrictModeを効果的に活用する方法をお伝えします。

コードレビューでのチェックポイント

// コードレビュー用のチェックリスト
const StrictModeChecklist = {
  useEffect: [
    'クリーンアップ関数が実装されているか',
    '依存配列が正しく設定されているか',
    '副作用がEffect内で実行されているか',
    'AbortControllerでリクエストをキャンセルしているか'
  ],
  
  useState: [
    'レンダリング中に状態更新していないか',
    '関数型更新を適切に使用しているか',
    '状態の依存関係が正しく管理されているか'
  ],
  
  customHooks: [
    'StrictModeでの重複実行に対応しているか',
    'メモリリークが発生しないか',
    '適切なクリーンアップが実装されているか'
  ],
  
  performance: [
    '重い計算がuseMemoでメモ化されているか',
    '不要な再レンダリングが防がれているか',
    'StrictModeでのパフォーマンス影響が考慮されているか'
  ]
};

このチェックリストを使うことで、体系的なコードレビューができます。

レビューの流れ

  1. StrictModeでの動作確認
  2. チェックリストでの確認
  3. パフォーマンステスト
  4. チーム共有とフィードバック

これにより、品質の高いコードを継続的に維持できます。

まとめ:StrictModeを活用して品質の高いReactアプリを作ろう

React StrictModeは、開発時の問題を早期発見し、将来のReactバージョンへの対応を支援する重要なツールです。

StrictModeの主な効果

問題の早期発見

  • 副作用の問題: useEffectでの不適切な処理
  • 状態管理の問題: 競合状態と無限ループ
  • メモリリークの問題: クリーンアップ不足
  • 非推奨APIの使用: 将来削除される機能

これらの問題を開発中に発見できることで、本番環境での障害を未然に防げます。

開発効率の向上

  • 品質向上: より堅牢で保守性の高いコード
  • 学習効果: Reactのベストプラクティスの習得
  • チーム統一: 一貫した開発基準の確立
  • 将来対応: 新しいReactバージョンへの準備

StrictModeを使うことで、開発チーム全体のスキル向上にもつながります。

実装のポイント

段階的な導入

  • まずは小さなコンポーネントから開始
  • 問題を一つずつ解決
  • チーム全体への展開

環境管理

  • 開発環境での条件付き有効化
  • 環境変数による設定管理
  • チーム共通の設定ファイル

エラー処理

  • Error Boundaryとの組み合わせ
  • 詳細なログ出力
  • ユーザーフレンドリーなエラー表示

今日から始められること

1. StrictModeの有効化

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

2. 基本的な問題の修正

  • useEffectにクリーンアップ関数を追加
  • 副作用をEffect内に移動
  • 状態更新の最適化

3. チームでの共有

  • 設定の統一
  • レビュー基準の策定
  • 継続的な改善

React StrictModeは現代のReact開発において必須のツールです。 適切に活用することで、より安全で保守性の高いReactアプリケーションを開発できます。

ぜひ、実際のプロジェクトでStrictModeを活用して、その効果を体験してみてください! きっと、開発の品質と効率が大きく向上するはずです。

関連記事