<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>
);
Strict Mode는 다음과 같은 개발 전용 동작을 활성화합니다.
- 순수하지 않은 렌더링으로 인해 발생하는 버그를 찾기 위해 컴포넌트가 추가로 다시 렌더링됩니다.
- Effect 클린업이 누락되어 발생하는 버그를 찾기 위해 컴포넌트가 추가로 Effect를 다시 실행합니다.
- 더 이상 사용되지 않는 API의 사용 여부를 확인하기 위해 컴포넌트를 검사합니다.
Props
StrictMode
는 props를 허용하지 않습니다.
주의 사항
<StrictMode>
로 래핑된 트리 내에서 Strict Mode를 해제할 수 있는 방법은 없습니다. 이를 통해<StrictMode>
내부의 모든 컴포넌트가 검사되었음을 확신할 수 있습니다. 제품을 개발하는 두 팀이 검사가 가치 있는지에 대해 의견이 갈리는 경우, 합의에 도달하거나<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는 실수로 작성된 순수하지 않은 코드를 찾아내기 위해 몇 가지 함수(순수 함수여야 하는 것만)를 개발 환경에서 두 번 호출합니다. 이에는 다음이 포함됩니다.
- 컴포넌트 함수 본문 (이벤트 핸들러 내부 코드를 포함하지 않도록 최상위 로직에서만)
useState
,set
함수,useMemo
, 또는useReducer
에 전달한 함수constructor
,render
,shouldComponentUpdate
와 같은 일부 클래스 컴포넌트 메소드 (전체 목록 보기)
함수가 순수한 경우 두 번 실행하여도 동작이 변경되지 않습니다. 순수 함수는 항상 같은 결과를 생성하기 때문입니다. 그러나 함수가 순수하지 않다면 (예: 받은 데이터를 변경하는 함수) 두 번 실행하면 보통 알아챌 수 있습니다. (이것이 순수하지 않은 이유입니다!) 이는 버그를 조기에 발견하고 수정하는 데 도움이 됩니다.
다음은 Strict Mode의 이중 렌더링이 어떻게 버그를 조기에 발견하는 데 도움이 되는지 보여주는 예시입니다.
StoryTray
컴포넌트는 stories
배열을 받아 마지막에 “이야기 만들기” 항목을 추가합니다.
export default function StoryTray({ stories }) { const items = stories; items.push({ id: 'create', label: '이야기 만들기' }); 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: '이야기 만들기' }); 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(); // 배열 복제
// ✅ Good: 새로운 배열에 추가
items.push({ id: 'create', label: '이야기 만들기' });
이렇게 하면 StoryTray
함수를 순수하게 만들 수 있습니다. 함수가 호출될 때마다 배열의 사본만 수정하고 외부 객체나 변수에 영향을 미치지 않습니다. 이렇게 하면 버그가 해결되지만 컴포넌트의 동작에 문제가 있다는 것이 명확해지기 전에 컴포넌트를 더 자주 다시 렌더링해야 했습니다.
원래 예시에서는 버그가 명확하지 않았습니다. 이제 원래 (버그가 있는) 코드를 <StrictMode>
로 래핑해 보겠습니다.
export default function StoryTray({ stories }) { const items = stories; items.push({ id: 'create', label: '이야기 만들기' }); return ( <ul> {items.map(story => ( <li key={story.id}> {story.label} </li> ))} </ul> ); }
Strict Mode에서는 항상 렌더링 함수를 두 번 호출하므로 실수를 바로 확인할 수 있습니다(“이야기 만들기”가 두 번 나타남). 따라서 프로세스 초기에 이러한 실수를 발견할 수 있습니다. 컴포넌트가 Strict Mode에서 렌더링되도록 수정하면 이전의 호버 기능과 같이 향후 발생할 수 있는 많은 프로덕션 버그도 수정할 수 있습니다.
import { useState } from 'react'; export default function StoryTray({ stories }) { const [isHover, setIsHover] = useState(false); const items = stories.slice(); // 배열 복제 items.push({ id: 'create', label: '이야기 만들기' }); 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는 Effects의 버그를 찾는 데도 도움이 될 수 있습니다.
모든 Effect에는 몇 가지 셋업 코드가 있고 어쩌면 클린업 코드가 있을 수 있습니다. 일반적으로 React는 컴포넌트가 마운트(화면에 추가)될 때 셋업을 호출하고 컴포넌트가 마운트 해제(화면에서 제거)될 때 클린업을 호출합니다. 그런 다음 React는 마지막 렌더링 이후로부터 의존성이 변경된 경우 클린업과 셋업을 다시 호출합니다.
Strict Mode가 켜져 있으면 React는 모든 Effect에 대해 개발 환경에서 한 번 더 셋업+클린업 사이클을 실행합니다. 의외로 느껴질 수도 있지만 수동으로 파악하기 어려운 미묘한 버그를 드러내는 데 도움이 됩니다.
다음은 Strict Mode에서 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> );
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 클린업을 구현하는 방법에 대해 자세히 알아보세요.
Strict Mode에 의해 활성화된 사용 중단 경고 수정
React는 <StrictMode>
트리 내부의 컴포넌트가 더 이상 사용되지 않는 다음 API 중 하나를 사용하는 경우 경고를 표시합니다.
findDOMNode
. 대안 보기UNSAFE_componentWillMount
와 같은UNSAFE_
클래스 생명주기 메서드. 대안 보기- 레거시 context (
childContextTypes
,contextTypes
, 및getChildContext
). 대안 보기 - 레거시 문자열 refs (
this.refs
). 대안 보기
이러한 API는 주로 이전 클래스 컴포넌트에서 사용되므로 최신 앱에서는 거의 나타나지 않습니다.