메인 콘텐츠로 건너뛰기

React Strict Mode 완벽 가이드: 더 나은 React 앱을 위한 필수 도구

wislan
wislan
조회수 142
요약

React 개발을 하다 보면 콘솔에 같은 로그가 두 번씩 찍히는 현상을 본 적이 있으신가요? 혹은 useEffect가 예상과 다르게 여러 번 실행되는 것을 경험하신 적이 있나요? 이는 버그가 아니라 React Strict Mode가 정상적으로 작동하고 있다는 신호입니다.

오늘은 React Strict Mode가 무엇인지, 왜 사용해야 하는지, 그리고 어떻게 활용하는지 상세히 알아보겠습니다.

React Strict Mode란 무엇인가?

React Strict Mode는 애플리케이션의 잠재적인 문제를 조기에 발견하도록 도와주는 개발 도구입니다. 이는 추가적인 검사와 경고를 활성화하여 개발자가 더 안전하고 미래 지향적인 코드를 작성할 수 있도록 지원합니다.

핵심 특징

개발 환경 전용: Strict Mode는 개발 환경에서만 작동하며, 프로덕션 빌드에는 포함되지 않습니다. 따라서 실제 사용자의 앱 성능에는 전혀 영향을 주지 않습니다.

시각적 변화 없음: UI를 렌더링하지 않으며, 오직 자식 컴포넌트들에 대한 추가 검사와 경고만 활성화합니다.

점진적 적용 가능: 앱 전체가 아닌 일부 컴포넌트에만 선택적으로 적용할 수 있습니다.

왜 Strict Mode를 사용해야 할까?

1. 미래를 위한 준비

React는 지속적으로 발전하고 있으며, Concurrent Features와 같은 새로운 기능들이 추가되고 있습니다. Strict Mode는 이러한 새로운 기능들과 호환되는 코드를 작성하도록 도와줍니다.

2. 숨겨진 버그 발견

일반적인 개발 과정에서는 놓치기 쉬운 미묘한 버그들을 조기에 발견할 수 있습니다. 특히 부작용(side effects)과 관련된 문제들을 효과적으로 찾아냅니다.

3. 베스트 프랙티스 강제

React 팀이 권장하는 패턴과 관행을 따르도록 유도하여, 더 유지보수하기 쉽고 안정적인 코드를 작성하게 됩니다.

Strict Mode가 감지하는 문제들

1. 안전하지 않은 생명주기 메서드

// 경고: componentWillMount는 deprecated
class OldComponent extends React.Component {
  componentWillMount() {
    console.log('This will trigger a warning in Strict Mode');
  }
  
  render() {
    return <div>Legacy Component</div>;
  }
}

2. 레거시 문자열 ref API

// 피해야 할 패턴
class BadRefExample extends React.Component {
  render() {
    return <input ref="myInput" />;
  }
}

// 권장 패턴
class GoodRefExample extends React.Component {
  inputRef = React.createRef();
  
  render() {
    return <input ref={this.inputRef} />;
  }
}

3. 예상치 못한 부작용

// Strict Mode에서 문제가 될 수 있는 코드
let renderCount = 0;

function ProblematicComponent() {
  renderCount++; // 순수하지 않은 렌더링 함수
  
  return <div>Rendered {renderCount} times</div>;
}

// 올바른 접근
function BetterComponent() {
  const [renderCount, setRenderCount] = useState(0);
  
  useEffect(() => {
    setRenderCount(prev => prev + 1);
  }, []);
  
  return <div>Rendered {renderCount} times</div>;
}

이중 렌더링의 이해

Strict Mode의 가장 눈에 띄는 특징은 의도적인 이중 렌더링입니다. 이는 버그가 아니라 기능입니다.

이중 실행되는 것들

개발 모드에서 다음 함수들이 두 번 실행됩니다:

  • 클래스 컴포넌트의 constructor, render, shouldComponentUpdate

  • 함수 컴포넌트의 본문

  • State updater 함수

  • useState, useMemo, useReducer에 전달된 함수

실제 예제

function DoubleRenderExample() {
  console.log('Component rendered'); // 개발 모드에서 2번 출력
  
  const [count, setCount] = useState(() => {
    console.log('State initializer'); // 2번 실행
    return 0;
  });
  
  const expensiveValue = useMemo(() => {
    console.log('Memo calculation'); // 2번 실행
    return count * 2;
  }, [count]);
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Double: {expensiveValue}</p>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

React 18의 새로운 Strict Mode 동작

React 18부터 Strict Mode는 더욱 엄격해졌습니다. 특히 Effect의 재실행을 통해 cleanup 함수의 중요성을 강조합니다.

Effect의 마운트-언마운트-재마운트 사이클

function React18StrictModeExample() {
  useEffect(() => {
    console.log('Effect 실행');
    
    const timer = setInterval(() => {
      console.log('Timer tick');
    }, 1000);
    
    // cleanup 함수가 제대로 작동하는지 검증
    return () => {
      console.log('Cleanup 실행');
      clearInterval(timer);
    };
  }, []);
  
  return <div>React 18 Strict Mode Example</div>;
}

개발 모드에서 이 컴포넌트는 다음 순서로 실행됩니다:

  1. 마운트 (Effect 실행)

  2. 언마운트 (Cleanup 실행)

  3. 재마운트 (Effect 다시 실행)

이를 통해 cleanup 함수가 제대로 작동하는지 확인할 수 있습니다.

Strict Mode 적용하기

전체 앱에 적용

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

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

부분적으로 적용

function App() {
  return (
    <div>
      <Header /> {/* Strict Mode 미적용 */}
      <React.StrictMode>
        <MainContent /> {/* Strict Mode 적용 */}
        <Sidebar />     {/* Strict Mode 적용 */}
      </React.StrictMode>
      <Footer /> {/* Strict Mode 미적용 */}
    </div>
  );
}

점진적 마이그레이션 전략

기존 프로젝트에 Strict Mode를 도입할 때는 다음과 같은 전략을 추천합니다:

// 1단계: 새로운 기능부터 적용
function NewFeature() {
  return (
    <React.StrictMode>
      <FeatureComponent />
    </React.StrictMode>
  );
}

// 2단계: 리팩토링하는 컴포넌트에 적용
function RefactoredComponent() {
  // 리팩토링 후 Strict Mode 적용
  return (
    <React.StrictMode>
      <UpdatedLogic />
    </React.StrictMode>
  );
}

// 3단계: 점진적으로 범위 확대
// 최종적으로 root 레벨에 적용

실전 팁과 주의사항

1. 콘솔 로그 중복 처리

개발 중 콘솔 로그가 두 번 찍히는 것이 불편하다면, 다음과 같은 방법을 사용할 수 있습니다:

// 개발 환경 체크 유틸리티
const isDevelopment = process.env.NODE_ENV === 'development';

// 커스텀 로그 함수
function devLog(...args) {
  if (isDevelopment && !window.__strictModeLogShown) {
    console.log(...args);
    window.__strictModeLogShown = true;
    setTimeout(() => {
      window.__strictModeLogShown = false;
    }, 0);
  }
}

2. 외부 라이브러리와의 호환성

일부 오래된 라이브러리는 Strict Mode와 호환되지 않을 수 있습니다:

function App() {
  return (
    <>
      <React.StrictMode>
        <ModernComponents />
      </React.StrictMode>
      {/* 레거시 라이브러리는 Strict Mode 밖에 배치 */}
      <LegacyLibraryComponent />
    </>
  );
}

3. 성능 측정 시 주의사항

개발 모드에서 성능을 측정할 때는 Strict Mode의 이중 렌더링을 고려해야 합니다:

function PerformanceAwareComponent() {
  useEffect(() => {
    if (process.env.NODE_ENV === 'production') {
      // 프로덕션에서만 정확한 성능 측정
      performance.mark('component-mounted');
    }
  }, []);
  
  return <ComplexComponent />;
}

자주 묻는 질문들

Q1: Strict Mode가 프로덕션 성능에 영향을 주나요?

아닙니다. Strict Mode는 개발 빌드에서만 활성화되며, 프로덕션 빌드에서는 완전히 제거됩니다.

Q2: useEffect가 두 번 실행되는 것을 막을 수 있나요?

개발 모드에서는 의도적인 동작이므로 막을 수 없고, 막아서도 안 됩니다. 대신 Effect와 cleanup이 올바르게 작동하도록 코드를 개선해야 합니다.

Q3: 모든 프로젝트에 Strict Mode를 사용해야 하나요?

새 프로젝트는 처음부터 Strict Mode를 사용하는 것이 좋습니다. 기존 프로젝트는 점진적으로 도입하되, 장기적으로는 전체 앱에 적용하는 것을 목표로 해야 합니다.

Q4: Strict Mode 경고를 무시해도 되나요?

경고는 잠재적인 문제를 나타내므로 가능한 한 해결해야 합니다. 특히 React의 향후 버전에서 breaking change가 될 수 있는 부분들입니다.

마치며

React Strict Mode는 단순한 디버깅 도구가 아니라, 더 나은 React 개발자가 되도록 도와주는 멘토와 같습니다. 처음에는 이중 렌더링과 추가 경고들이 번거롭게 느껴질 수 있지만, 이를 통해 발견하고 수정한 버그들과 개선된 코드 품질을 경험하면 그 가치를 충분히 느낄 수 있을 것입니다.

React의 미래는 Concurrent Features와 같은 혁신적인 기능들로 가득합니다. Strict Mode를 활용하여 이러한 미래에 준비된, 견고한 React 애플리케이션을 구축하시기 바랍니다.


참고 자료