프론트엔드 기본개념 복습/React

[React] 에러 경계 (Error Boundaries)

콘요맘떼 2022. 2. 24. 16:06

이미지 출처 : https://velopert.com/3631

 

  리액트를 개발해본 사람이라면 누구나 한 번쯤 접했을 화면이다. 리액트는 컴포넌트 내부에서 에러가 발생하면 위와 같이 에러를 화면으로 보여준다. 그러나 저 화면은 개발을 하는 우리들만이 볼 수 있는 화면이다. Production 모드로 들어가면 에러는 보여지지 않고 렌더링에 실패한 컴포넌트가 남긴 쓸쓸한 텅 빈 화면만이 남아있을 것이다. 그에 따라 React16에서는 UI 일부분에 해당하는 자바스크립트 에러가 전체 어플리케이션을 셧다운시키는 것을 막기 위해서 에러 경계라는 새로운 개념을 가져왔다. (React 공식 문서에 적힌 표현)

  에러 경계(Error Boundary)는 하위 컴포넌트 트리 안에서 발생하는 에러를 찾아내고 깨진 컴포넌트 트리 대신 fallback UI를 제공해준다. 복잡할 것 없이 에러 경계 역시 하나의 컴포넌트이며 자식 컴포넌트에서 에러가 발생하면 그 에러를 감지하고 미리 준비해놓은 대체 화면을 제공해준다고 생각하면 된다. 참고로 에러는 발생한 위치로부터 가장 가까운 에러 경계로 전파된다.

 

 

생명주기 메소드

  에러 경계는 생명주기 메소드를 기반으로 작동한다. 특정 클래스 컴포넌트 안에 static getDerivedStateFromError( ) 혹은 componentDidCatch( )메소드 (혹은 둘 다)를 정의하면 해당 컴포넌트는 에러경계 컴포넌트가 된다.

 

1. static getDerivedStateFromError(error)

  하위의 자손 컴포넌트에서 오류가 발생했을 때 호출된다. 매개변수로 오류를 전달받으며 에러 경계 컴포넌트의 갱신된 state를 반드시 반환해야 한다. 따라서 에러가 발생한 경우 getDerivedStateFromError( ) 메소드를 활용하여 에러 발생 유무를 state로 관리하고 그에 따라 fallback UI를 제공하도록 설정할 수 있다. 해당 메소드는 render 단계에서 호출되는 메소드이기 때문에 Side Effect를 발생시키면 안 된다는 점을 주의해야 한다.

 

 

2. componentDidCatch(error, info)

  이 메소드는 두 가지 매개변수를 전달받는다. error은 오류 내용을, info는 오류를 발생시킨 컴포넌트에 대한 정보를 담고 있다. componentDidCatch( )는 커밋 단계에서 호출되기 때문에 Side Effect를 발생시킬 수 있다. 따라서 에러의 로그를 남기는 등의 작업은 componentDidCatch( ) 메소드를 통해 구현하면 된다. componentDidCatch( )가 에러를 핸들링하는 것은 development 모드와 production 모드에서 다른 모습을 보여준다. 개발 모드인 경우 window.onerror 혹은 window.addEventListener('error', callback)과 같은 에러 핸들러들이 componentDidCatch( )가 잡은 오류를 인터셉트한다. 그러나 프로덕션 모드에서 componentDidCatch( )가 캐치하는 에러들은 해당 메소드에 의해 처리되며 그 외의 에러들만이 상위 에러 핸들러로 전달된다.

 

static getDerivedStateFromError과 componentDidCatch를 함께 활용하여 fallback UI와 에러 로깅을 구현하는 에러 경계 만들기

 

  이렇게 에러 경계를 만들어줬다면 일반적인 부모 컴포넌트처럼 에러가 발생할 수 있는 컴포넌트를 에러 경계로 감싸주면 된다. (wrapping) 에러 경계는 최상위 컴포넌트를 감쌀 수도 있고 에러가 발생할 수 있는 각각의 컴포넌트들을 하나하나 감싸주는 방법도 존재한다.

 

 

 

주의할 점

1. 에러 경계에서 캐치하지 못한 에러가 나오면 리액트 컴포넌트 트리가 통째로 unmount된다.

React 16부터 등장한 변경점이다. 이러한 변경이 생긴 이유는 손상된 UI를 남겨두는 것이 오히려 사용자 경험에 안 좋은 영향을 끼치며 예상치 못한 오류들을 발생시킬 수도 있기 때문이다.

 

2. 에러 경계는 다음 에러들을 포착하지 않는다.

(1) 이벤트 핸들러

  이벤트 핸들러에서 발생한 에러는 렌더링 상에서는 문제가 되지 않기 때문이다. 따라서 이벤트 핸들러에서 발생하는 에러는 try/catch문으로 핸들링하면 된다.

(2) 비동기 코드 (ex. setTimeout, requestAnimationFrame ... )

  기능적으로는 에러가 발생하지만 마찬가지로 화면은 제공되기 때문이다. 어떻게 본다면 에러 경계는 화면에 무언가를 보여주는 데 실패한 에러에 대해 핸들링한다고 보면 간단할 것 같다.

(3) SSR (서버 사이드 렌더링)

(4) 자식이 아니라 에러 경계 그 자체에서 발생하는 에러

 

3. 에러 경계는 아직 클래스 컴포넌트로만!

리액트 17.0.2 버전 기준 아직 에러 경계는 클래스 컴포넌트로만 구현이 가능하다. 이는 에러 경계의 동작 원리를 담당하는 getDerivedFromError, componentDidCatch 메소드가 아직 훅으로 구현되지 못했기 때문이다. 그러나 추가 계획에 대한 언급이 존재하기는 한다. (https://reactjs.org/docs/hooks-faq.html#do-hooks-cover-all-use-cases-for-classes - Do Hooks cover all use cases for classes? 질문 참조)