React - Strict Mode

이소라·2022년 9월 20일
0

React

목록 보기
18/23

Strict Mode

  • 애플리케이션 내의 잠재적인 문제를 알아내기 위한 도구
  • Fragement와 같이 UI를 렌더링하지 않으며, 자손들에 대한 부가적인 검사와 경고를 활성화함
  • development mode에서만 활성화되기 때문에, production build에는 영향을 끼치지 않음
  • 애플리케이션 내 어디서든지 strictMode를 활성화할 수 있음
import React from 'react';

function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>
      <Footer />
    </div>
  );
}
  • 위 예시에서 Header와 Footer 컴포넌트는 StrictMode 검사가 이루어지지 않지만, ComponentOne과 ComponentTwo 컴포넌트는 각각 자손까지 StrictMode 검사가 이루어짐

  • StrictMode가 검사하는 항목들
    1. 안전하지 않은 생명주기를 사용하는 컴포넌트 발견
    2. 레거시 문자열 ref 사용에 대한 경고
    3. 권장되지 않은 findDOMNode 사용에 대한 경고
    4. 예상치 못한 side effect 검사
    5. 레거시 context API 검사
    6. 재사용 가능한 상태를 보장



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

  • 불안전한 코드 실행을 유발하는 생명주기 메서드가 존재함

    • componentWillMount
    • componentWillReceiveProps
    • componentWillUpdate
  • 이 생명주기 메서드들은 잘못 이해하고 잘못 사용하기 쉬우며, 비동기 렌더링과 함께 잘못 사용할 경우 문제가 될 수 있음

  • 애플리케이션이 3rd party 라이브러리를 사용한다면, 해당 생명주기 메서드가 사용되지 않는다고 장담하기 어려움

    • 이러한 경우, StrictMode가 도움이 됨
    • StrictMode가 활성화되면, React는 안전하지 않은 생명주기 메서드를 사용하는 모든 클래스 컴포넌트 목록들 정리하여 컴포넌트에 대한 정보가 담긴 긴 경고 로그를 출력함

  • StrictMode에 의해 발견된 문제를 해결한다면, 향후 릴리즈되는 React에서 동시 발생하는(concurrent) 렌더링의 이점을 얻을 수 있음

Concurrent Mode

  • Concurrent Mode

    • 동시성 : 여러 작업을 작은 단위로 나눈 뒤, 그들 간의 우선순위를 정하고 그에 따라 작업을 번갈아 수행하는 방법
    • 작업 간의 전환이 매우 빠르게 이루어지면서 동시에 수행되는 것처럼 보이는 것
  • 문제점 1 : 기존의 debounce와 throttle은 한계점이 존재함

    • debounce : 사용자의 마지막 입력이 끝난 뒤 일정 시간이 지나고 무거운 작업을 수행하는 방법, 기기의 성능과 관계없이 무조건 일정 시간을 기다려야 한다는 단점이 있음
    • throttle : 입력 중에 주기적으로 무거운 작업을 수행하는 방법, throttle 주기가 짧을 수록 성능이 좋은 기기는 사용자 경험을 높일 수 있지만, 성능이 나쁜 기기는 버벅거림이 심해짐
  • concurrent mode는 debounce와 throttle의 한계점을 동시성으로 해결함

    • 작업 간 빠른 전환으로 사용자 입력과 무거운 작업이 버벅대지 않고 동시에 처리되는 경험을 사용자에게 줄 수 있음
    • 작업 처리 속도는 개발자가 설정한 delay에 의존하는 것이 아니라 사용자의 기기 성능에 좌우됨
  • 문제점 2 : 렌더링이 충분히 빠름에도 의미 없는 로딩을 보여주는 경우가 존재함

    • suspense로만 구현된 로딩은 이전 페이지를 유저로부터 차단한 뒤, 다음 페이지의 전체 로딩 화면으로 대체하므로 사용자가 답답함을 느낌
  • concurrent mode는 일정 시간 동안 현재 페이지와 기능들을 유지하고, 다음 페이지에 대한 렌더링을 동시에 진행함으로써 해결함
    - 그리고 다음페이지의 렌더링 단계가 특정 조건에 부합하면, 해당 페이지를 렌더링함
    - 특정 state 변경에 대한 현 화면의 UI 렌더링 단계보다 더 최신 단계로 진행하여야 실제 DOM에 반영함


StricMode : 레거시 문자열 ref 사용에 대한 경고

  • 이전 React에서는 ref를 관리하는 방법으로 레거시 문자열 ref API와 콜백 API를 제공했음
    • 문자열 ref가 사용하기 편리하지만 단점이 있어서 콜백 API 사용을 권장했음
  • React 16.3에서 createRef API가 도입되면서 객체 ref를 사용할 수 있게 됨
    • createRef API와 별개로 콜백 API는 계속 지원됨
class MyComponent extends React.Component {
  constructor(props) {
    super(props);

    this.inputRef = React.createRef();
  }

  render() {
    return <input type="text" ref={this.inputRef} />;
  }

  componentDidMount() {
    this.inputRef.current.focus();
  }
}
  • 이제는 객체 ref가 문자열 ref를 교체하는 용도로 쓰이기 때문에, StrictMode는 문자열 ref의 사용에 대해 경고함

StricMode : 권장되지 않는 findDOMNode 사용에 대한 경고

  • 이전의 React에서는 주어진 클래스 인스턴스를 바탕으로 트리를 탐색해 DOM노드를 찾을 수 있는 findDOMNode를 지원했음

    • findDOMNode는 클래스 컴포넌트에서 사용할 수 있지만, 부모가 특정 자식을 렌더링하도록 허락하기 때문에 추상화 레벨이 무너짐
      • 이로 인해 부모가 자식의 DOM 노드에 닿을 수 있기 때문에, 컴포넌트의 세세한 구현을 변경할 수 없으므로 리팩토링이 어려움
    • findDOMNode는 일회용, 읽기 전용 API이기 때문에, API를 호출했을 때만 값을 반환함
      • 자식 컴포넌트가 다른 노드를 렌더링할 경우, 변경 사항에 대응할 방법이 없음
      • 그러므로 findDOMNode는 항상 변하지 않는 단일 DOM 노드를 반환하는 컴포넌트에서만 정상적으로 작동함
  • 이제는 DOM노드에 바로 ref를 지정할 수 있으므로, 보통 findDOMNode가 필요하지 않음

    • DOM 노드를 감싸는 wrapper를 만들어서 ref를 바로 붙이는 것도 가능함
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.wrapper = React.createRef();
  }
  render() {
    return <div ref={this.wrapper}>{this.props.children}</div>;
  }
}

StricMode : 예상치 못한 side effect 검사

  • React는 개념적으로 두 단계로 동작함

    1. 렌더링 단계
      • 특정 환경(DOM)에 어떤 변화가 필요한지 결정하는 단계
      • React는 이 단계에서 render를 호출하여 이전 렌더링 결과와 현재 렌더링 결과를 비교함
    2. 커밋 단계
      • React가 변경 사항(DOM 노드 추가, 변경, 제거 등)을 반영하는 단계
      • React는 이 단계에서 componentDidMountcomponentDidUptate와 같은 생명주기 메서드를 호출함
  • 커밋 단계는 일반적으로 매우 빠르지만, 렌더링 단계는 느릴 수 있음

    • 곧 추가될 concurrent 모드는 렌더링 작업을 더 작은 단위로 나누고, 작업을 중지했다가 재개하는 방식으로 브라우저가 멈추는 것을 피함
    • 즉, React는 커밋하기 전에 렌더링 단계의 생명주기 메서드를 여러 번 호출하거나 아예 커밋하지 않을 수도(에러나 우선순위에 따른 작업 중단) 있음
  • 렌더링 단계의 생명주기 메서드는 다음과 같음

    • constrcutor
    • componentWillMount (or UNSAFE_componentWillMount)
    • componentWillReceiveProps (or UNSAFE_componetWillReceiveProps)
    • componentWillUpdate (or UNSAFE_componetWillUpdate)
    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • setState 업데이트 함수 (첫 번째 인자)
  • 위 메서드들은 여러번 호출될 수 있기 때문에, 메서드에 side-effect를 포함하지 않는 것이 중요함

    • 이 규칙을 무시할 경우, 메모리 누수나 애플리케이션의 잘못된 상태 등의 문제를 일으킬 수 있음
    • 불행히도 이러한 문제들은 예측한대로 동작하지 않기 때문에 발견하기 어려움

  • StrictMode가 자동으로 side effect를 찾아주는 것은 불가능하지만, side effect를 예측할 수 있게 만들어서 문제되는 부분을 발견할 수 있게 도와줌
  • StrictMode는 아래의 함수들을 고의적으로 2번 호출함을 통해서 문제되는 부분을 발견함
    • 클래스 컴포넌트의 constructor, render, shouldComponentUpdate
    • 클래스 컴포넌트의 정적 메서드 getDrivedStateFromProps
    • 함수 컴포넌트의 body
    • state 업데이트 함수 (`setState의 첫 번째 인수)
    • useState, useMemo, useReducer에 전달되는 함수
  • development mode에서만 위 메서드들이 2번 호출됨 (production mode에서는 그렇지 않음)
class TopLevelRoute extends React.Component {
  constructor(props) {
    super(props);

    SharedApplicationState.recordEvent('ExampleComponent');
  }
}
  • 위 코드에서 SharedApplicationState.recordEvent가 여러 번 호출했을 때 연산 결과가 달라진다면, 이 컴포넌트를 여러 번 인스턴스화하는 것은 애플리케이션의 잘못된 상태를 이끌어낼 수 있음
    • 이와 같은 이해하기 어려운 버그들은 개발 중에 나타나지 않을 수도 있고, 일관성이 없어서 발견하지 못할 수도 있음
    • StrictMode는 컴포넌트의 constructor와 같은 메서드를 의도적으로 2번 호출함으로써 이와 같은 패턴을 쉽게 찾을 수 있게 함

StrictMode : 레거시 context API 검사

  • 레거시 context API는 에러를 잘 발생시키므로 React의 이후 릴리즈에서는 삭제될 예정임
    • React 16.x 버전에서 레거시 context API가 여전히 동작하지만, StrictMode에서는 아래와 같은 경고 메세지를 보여줌
    • createContext API를 사용해서 migration하는 것을 권장함


StrictMode : 재사용 가능한 상태를 보장

  • 미래에는 React가 상태를 유지하면서 UI section을 추가하고 삭제하는 기능을 추가할 예정임

    • 예를 들어, 스크린에서 사용자 탭이 사라졌다가 돌아왔을 때, React는 이전 스크린을 바로 보여줄 수 있어야 함
    • 이를 위해서, React는 마운트 해제 전 사용된 컴포넌트 상태를 사용해서 트리를 다시 마운트하는 것을 지원해야함
      • 이 기능은 컴포넌트가 마운트되었을 때의 effect를 유지했다가 삭제하는 것을 여러 번 요구됨
      • 어떤 effect는 삭제하는 콜백에서 구독이 잘 정리되지 않을 수 있고, effect가 마운트만 되고나, 한번만 삭제되는 것을 암시적으로 가정할 수도 있음
  • 이러한 문제를 해결하기 위해서, React 18은 StrictMode에서의 새로운 development mode check를 도입함

    • 각 컴포넌트가 마운트 해제되고 다시 마운드될 때 check함
    • 컴포넌트가 처음으로 마운트되었을 때, 다음 마운트 때 이전 상태를 회복함
  • StrictMode의 새로운 check를 사용하지 않을 때, 컴포넌트가 마운트되었을 때, React가 생성하는 effect의 예시

  1. React가 컴포넌트를 마운트함
    1-1. Layout effects가 생성됨
    1-2. Effects가 생성됨
  • StrictMode의 새로운 check를 사용할 때, 컴포넌트가 마운트되었을 때, React가 생성하는 effect의 예시
  1. React가 컴포넌트를 마운트함
    1-1. Layout effects가 생성됨
    1-2. Effects가 생성됨
  2. React가 마운트되었던 컴포넌트의 effect를 삭제함
    2-1. Layout effects가 삭제됨
    2-2. Effects가 삭제됨
  3. React가 다운드되었던 컴포넌트를 재생성함
    3-1. Layout effects가 생성됨
    3-2. Effect setup 코드가 실행됨
  • 위 예시를 통해서 React는 두번째 마운트 때 첫 번째 마운트로부터의 상태를 회복시킨다는 것을 알 수 있음

    • 이 기능은 사용자 탭이 스크린에서 없어졌다가 다시 돌아오는 것과 같은 사용자 동작을 나타내기 위해 코드가 상태 회복을 수행함
  • 컴포넌트의 마운트가 해제될 때, effect는 일반적으로 삭제됨

  1. React가 컴포넌트의 마운트를 해제함
    1-1. Layout effects가 삭제됨
    1-2. Effects가 삭제됨

0개의 댓글