리액트 Error Boundaries

박지원·2021년 12월 10일
1

리액트 에러 바운더리

소개

React 15 이전에서의 동작은 컴포넌트 내부의 자바스크립트 에러가 React내부 상태를 훼손하고 다음 렌더링시 emit cryptic errors를 유발했고 리액트는 이를 처리하는 방법을 제공하지 않았습니다. 하지만 이렇게 UI의 일부분에 존재하는 자바스크립트의 에러가 전체 애플리케이션을 중단시켜서는 안됩니다.


예를 들어 이렇게 한 컴포넌트에서 에러가 발생했을 때, webpack의 설정이 development mode로 되어있기 때문에 이렇게 보여줄 뿐 입니다.

실제로 사용자들이 접하게 될 화면은 이런 빈 화면만 마주하게 됩니다.

이는 잘못된 정보를 사용자에게 보여주는 것 보다 그냥 안보여주는게 더 나은 사용자 경험을 제공하기 때문입니다. 하지만 이런 빈 화면 보다 error boundaries를 추가해서 문제 발생시에 더 나은 사용자 경험을 제공할 수 있습니다.

에러 경계는 하위 컴포넌트 트리 어디에서든 자바스크립트 에러를 기록하며 깨진 컴포넌트 트리 대신 폴백 UI를 보여주는 React 컴포넌트 입니다. 그리고 에러 경계는 렌더링 도중 생명주기 메서드 및 그 아래의 전체 트리에서 에러를 잡아냅니다.

주의

에러 경계는 아래의 상황에서는 에러를 포착하지 않습니다.

1. 이벤트 핸들러

이벤트 핸들러는 render 메서드나 여타 다른 생명주기 메서드와 다르게 렌더링 중에 발생하는 것이 아닙니다.
따라서 이벤트 핸들러가 에러를 발생시켜도 React는 여전히 화면에 무엇을 표시해야할 지 잘 알고 있습니다.

개발 환경에서는 이런 에러를 던져주겠지만, X 버튼을 눌러 실제 표시되는 환경을 확인해보면

해당 핸들러는 잘 동작하지 않지만 화면은 제대로 표시되고 있음을 알 수 있습니다.

따라서 해당 이벤트 핸들러 내에서 에러를 잡아야하는 경우라면 try..catch 구문을 이용해서 에러를 잡으면 됩니다.

2. 비동기적 코드

예를들어 setTimeout 혹은 requestAnimationFrame 콜백과 같은 비동기적인 코드에서는 에러를 포착하지 않습니다.

setTimeout의 콜백함수에서 에러를 발생시켜보니,


에러는 발생하지만 여전히 화면은 정상적으로 뜨고 있습니다.

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

4. 자식이 아닌 에러 경계 자체에서 발생하는 에러


생명주기 메서드인 static getDerivedStateFromError()와 componentDidCatch()중 하나 (혹은 둘 다)를 정의하면 클래스 컴포넌트 자체가 에러 경계가 됩니다.

  • 에러가 발생한 뒤 폴백 UI를 렌더링 하려면 static getDerivedStateFromError()
  • 에러 정보를 기록하려면 componentDidCatch()

static getDerivedStateFromError()

static getDerivedStateFromError(error)

이 생명주기 메서드는 하위의 자손 컴포넌트에서 오류가 발생했을 때 호출됩니다. 이 메서드는 매개변수로 오류를 전달받고, 갱신된 state값을 반드시 반환해야합니다.

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }

    static getDerivedStateFromError(error) {
        return { hasError: true };
    }
    render() {
        if (this.state.hasError) {
            return <h1>에러 났어용</h1>;
        }

        return this.props.children;
    }
}

이 메서드는 render 단계에서 호출되므로, 부수 효과를 발생시키면 안됩니다. 해당 경우에는 componentDidCatch()를 사용해야합니다.

componentDidCatch()

componentDidCatch(error, info)

이 생명주기 메서드는 자손 컴포넌트에서 오류가 발생했을 때에 호출되며 2개의 매개변수를 전달받습니다.

  1. error - 발생한 오류
  2. info - 어떤 컴포넌트가 오류를 발생시켰는지에 대한 정보를 포함한 componentStack 키를 갖고 있는 객체

이 메서드는 commit 단계에서 호출되므로 부수효과를 발생시켜도 됩니다.

class ErrorBoundary extends React.Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false };
    }
    componentDidCatch(error, info) {
        로그기록하는컴포넌트(info.componentStack);
    }
    static getDerivedStateFromError(error) {
        return { hasError: true };
    }
    render() {
        if (this.state.hasError) {
            return <h1>에러 났어용</h1>;
        }

        return this.props.children;
    }
}

React의 componentDidCatch()가 오류를 처리하는 방식은 production mode와 development mode 의 build가 약간 다릅니다.

development build 에서는 error가 window까지 bubble up 됩니다. 이는 window.onerror나 window.addEventListener('error', callback)가 componentDidCatch()에서 잡은 오류를 가로채는 것을 의미합니다.

그러나 production modde의 build에서는 오류는 전파되지 않습니다. 상위 오류 핸들러는 componentDidCatch()에서 잡지 못한 오류만 받습니다.

오류 이벤트 내에서는 setState() 호출로 componentDidCatch()로 구현된 대체 UI를 렌더링할 수는 있지만 이후 릴리즈에서는 사용할 수 없게 될것입니다 대체 UI렌더링 제어는 static getDerivedStateFromError()를 대신 사용해야합니다.

에러 경계 배치 위치

이런 세분화 부분은 개발자에게 달려있는 부분입니다. 최상위 컴포넌트를 감싸서 문제 발생 메세지를 보여줄 수도 있고 각 위젯을 여러 에러 경계로 감싸서 보호할 수도 있습니다.

컴포넌트 스택 추적

React는 애플리케이션이 실수로 에러를 집어삼킨 경우에도 개발과정에서 렌더링하는 동안 발생한 모든 에러를 콘솔에 출력합니다. 에러 메시지 및 자바스크립트 스택과 더불어서 React 16은 컴포넌트 스택 추적 또한 제공합니다. 컴포넌트 추적 스택 내에서는 파일 이름과 몇 번째 줄인지도 알 수 있습니다.

development mode에서 componentDidCatch()에서 log를 안찍어줘도 에러 정보가 나오는 의문이 풀렸습니다.

하지만 이 기능은 개발 단계를 위해서만 제작되었으며 production 환경에서는 비활성화 해야합니다.


production mode

0개의 댓글