React.StrictMode 알아보기

Posted by Hyewon Kang on Fri, Dec 23, 2022

프로젝트를 진행하다가 API 요청이 두번씩 간다던가, socket emit이 두 번 보내지는 경우를 겪은 적이 많다. console.log()가 두 번씩 찍히는 경험도 많다. 이는 index.tsx 파일의 React.StrictMode가 동작하기 때문인데 그동안 단순히 주석 처리를 하고 strict 모드를 비활성화한 상태로 이용하곤 했다. 이를 지우고 프로젝트를 실행해도 별다른 문제는 생기지 않는다.

그렇다면 StrictMode는 왜 있는 것이며, 이렇게 지우고 사용해도 괜찮은 것일까.

 1import React from 'react';
 2import ReactDOM from 'react-dom/client';
 3import './index.css';
 4import App from './App';
 5
 6const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
 7root.render(
 8    // <React.StrictMode>
 9    <App />,
10    // </React.StrictMode>,
11);

Strict 모드

우선 Strict Mode에 대해 정리해보자.

리액트 공식 문서에 따르면, “StrictMode는 애플리케이션 내의 잠재적인 문제를 알아내기 위한 도구이다. Strict 모드는 개발 모드에서만 활성화되기 때문에, 프로덕션 빌드에는 영향을 끼치지 않는다.”

StrictMode는 index.tsx 외에도 프로젝트 내 어디서든 해당 모드의 활성화가 가능하다.

 1import React from 'react';
 2
 3function ExampleApplication() {
 4    return (
 5        <div>
 6            <Header />
 7            <div>
 8                **
 9                <React.StrictMode>
10                    **
11                    <ComponentOne />
12                    <ComponentTwo />
13                    **
14                </React.StrictMode>
15                **
16            </div>
17            <Footer />
18        </div>
19    );
20}

위의 예에서는 ComponentOne과 ComponentTwo의 각각의 자손까지 Strict 모드 검사가 이루어진다고 한다. JavaScript에도 비슷한게 있다. 코드 파일 상단에 “use strict”를 써 놓으면 JS를 실행할 때 조금 더 엄격한 코드 검사가 이루어진다.

Strict 모드가 무언가 검사하기 위한 목적으로 사용되는 도구인지는 알겠다. 그럼 어떤 부분에서 실질적인 도움이 될까?


Strict 모드의 검사항목

공식문서에서 Strict모드의 현재 총 6가지 검사 항목을 안내한다. 그 중 세가지에 대해 알아봤다. (나머지 세가지 항목은 알고 있지 않아도 될 것 같아서 ..)

나머지 3가지 항목

  • 레거시 문자열 ref 사용에 대한 경고
  • 레거시 context API 검사
  • Ensuring reusable state (추후 리액트에 적용될 예정)

1. 안전하지 않은 생명주기를 사용하는 컴포넌트 발견

deprecated된 메서드의 사용을 막아준다. 예로 componentWillMount, componentWillReceiveProps, componentWillUpdate와 같은 메서드를 사용자가 사용할 경우 경고 메세지를 띄워줄 수 있다.

Untitled

특히 서드 파티 라이브러리를 사용할 때 해당 생명주기 함수들이 사용되지 않는다고 장담하기 어렵다. 또한, Strict 모드를 쓴다면 리액트가 향후 Release될 때 concurrent 렌더링의 이점을 얻을 수 있다.

2. 권장되지 않는 findDOMNode 사용에 대한 경고

findDOMNode는 한때 React에서 허용되었지만 이제는 더이상 사용되지 않는다. 이는 DOM노드에 직접 ref를 지정해 사용할 수 있게 되었기 때문이다. 현재 이를 사용하면 StrictMode에서는 ref 사용을 권장한다.

  • findDOMNode : 주어진 클래스 인스턴스를 바탕으로 트리를 탐색해 DOM 노드를 찾는 메서드

3. 예상치 못한 부작용 검사

개념적으로 React는 두 단계로 동작합니다.

  • 렌더링 단계는 특정 환경(예를 들어, DOM과 같이)에 어떤 변화가 필요한 지 결정하는 단계입니다.(expensive part) 이 과정에서 React는 render를 호출하여 이전 렌더와 결과값을 비교합니다. (*un a render lifecycle which includes: constructorcomponentWillMountcomponentWillReceiveProps,  componentWillUpdategetDerivedStateFromPropsshouldComponentUpdaterender,  setState updater functions)
  • 커밋 단계는 React가 변경 사항을 반영하는 단계입니다(React DOM의 경우 React가 DOM 노드를 추가, 변경 및 제거하는 단계를 말합니다). 이 단계에서 React는 componentDidMount 나 componentDidUpdate 와 같은 생명주기 메서드를 호출합니다.*

즉, 렌더링 단계에서는 변화를 계산하는 단계다. 예를 들어 render 함수를 호출해 이전값과 비교하는 방식이다. 이렇게 계산한 변경을 반영하는 단계가 커밋이다.

이러한 단계는 동시 모드에서 동일하게 유지되지만, 렌더 단계를 작은 조각으로 분할하여 렌더 단계를 일시 중지했다가 재개하여 긴 렌더링으로 브라우저를 차단하지 않도록 할 계획이다. 실제로 이는 React가 변경 사항을 커밋하기 전에 두 번 이상 렌더 라이프사이클을 트리거하거나 커밋하지 않고 렌더를 트리거할 수 있음을 의미한다(예: 오류가 발생했거나 상위 이벤트가 발생한 경우). 따라서 렌더 단계의 라이프사이클은 한 번 이상 호출할 수 있으므로 부작용을 포함하지 않는 것이 중요하다. 부작용을 포함할 경우 메모리 누수 및 잘못된 상태를 유발할 수 있다.

이를 돕기 위해 개발 모드에서만 StrictMode는 라이프사이클에서 다음 메서드인 생성자, 렌더링, 상태 업데이트 프로그램 기능 설정 및 DerivatedStateFromProps를 두 번 호출하여 코드가 예상과 다르게 손상되거나 동작하는지 확인할 수 있다.

Strict 모드가 자동으로 부작용을 찾아주는 것은 불가능하다. 하지만, 조금 더 예측할 수 있게끔 문제가 되는 부분을 발견하게 한다. 이를 위해 아래의 함수를 의도적으로 이중으로 호출하여 찾을 수 있다. strictMode가 그냥 두 번 렌더링 하면서 검사하는 건줄 알았는데, 렌더링 단계의 메서드를 의도적으로 두번씩 호출한다는 사실을 처음 알았다.

클래스 컴포넌트의 constructorrender 그리고 shouldComponentUpdate 메서드

  • 클래스 컴포넌트의 getDerivedStateFromProps static 메서드
  • 함수 컴포넌트 바디
  • State updater 함수 (setState의 첫 번째 인자)
  • useStateuseMemo 그리고 useReducer에 전달되는 함수

두번 호출한다고 부작용을 찾을 수 있나?

이해 못하는 나같은 사람을 위해 예시 코드를 공식 문서에서 보여준다.

1class TopLevelRoute extends React.Component {
2    constructor(props) {
3        super(props);
4
5        SharedApplicationState.recordEvent('ExampleComponent');
6    }
7}

컴포넌트의 constructor와 같은 메서드를 의도적으로 두 번 호출하면 strict mode가 이와 같은 패턴을 쉽게 찾을 수 있도록 한다.

얼핏 보면 이 코드에는 문제가 없어 보인다. 하지만, SharedApplicationState.recordEvent의 연산 결과가 계속 달라진다면, 이 컴포넌트를 여러 번 인스턴스 화했을 때 애플리케이션의 상태를 잘못된 방향으로 이끌 수 있다. 이와 같은 이해하기 어려운 버그들은 개발 중에 나타나지 않을 수도 있고, 일관성이 없어 발견하지 못할 수도 있다.

즉, 의도적으로 두번 호출시키면서 개발자가 오류를 발견할 수 있도록 도움을 주는 방식으로 정리할 수 있겠다.


결론

프로젝트가 개발 모드일 때만 StrictMode가 동작한다. 이는 잠재적인 이슈를 발견해주는 역할을 하기 때문에 굳이 지우고 사용할 이유는 없지만, 이를 지우고 쓰지 말란 이야기는 없다. 용도는 알았으니 위의 항목들을 검사할 때를 제외하곤 필요에 의해 지우고 사용해도 될 것 같다.


Reference

Strict 모드 - React

Wait, you’re not using ?!