티스토리 뷰

반응형

프런트엔드 error를 확인하는 방법은 여러 가지가 있다. 우리는 기본적으로 개발자도구에 console에 발생하는 에러를 통해서 주로 error를 확인한다. 하지만 무작정 error handling을 try catch로 하는 것은 좋은 방식이 아니다. try catch로 해야 하는 부분과 Error boundaries를 활용하는 방법을 알아보고, 리액트라는 선언형 코드에서 사용하는 코드에 대해 알아보자. 이 글을 통해 스스로 내 프로젝트에 error를 넣어주는 방식에 대한 기초를 알아보자.

 

 

1. try catch 

javascript를 배운사람은 다 알 수 있는 에러 처리 방법이다. 보통은 nodejs 백엔드에서 api 호출 시 많이 사용한다. 그러면 react에서도 사용하면 될까? 반은 맞고 반은 아니다. react는 선언형(declarative) 프로그램이다. 선언형이기 때문에 세부로직보다는, 어떤 컴포넌트가 렌더링이 될지에 대해 더욱더 생각한다. 더 쉽게 이야기하면, JSX라는 캡슐화된 코드를 사용하기 때문에, JSX의 로직을 모르고 사용하고 싶을 때 선언하여 사용하면 되는 것이다. 기본적으로 try catch를 사용하는 시기는 선언형 보다는 명령형(imperative) 프로그램에서 많이 사용한다. 선언적 특성을 가진 리액트는 아래에서 소개할 Error boundaries를 사용한다. 만약 setState에 의한 componentDidUpdate 에서 에러가 발생할 경우에 우리는 Error boundaries에서 Error를 확인하는 것이 가능하다.

 

물론 try catch를 react에서 사용해야하는 경우도 있다. 바로 Event Handler의 경우이다. Event Handler가 아닌, 선언형에서는 Error boundaries를 사용하면 된다. 다시 말하면, 선언형이라는 말은 JSX와 같이 렌더링 되는 과정이나, lifecycle method (componentDidUpdate, useEffect 등)에 관련된 코드인 경우에만 해당이 된다고 볼 수 있다. 정리하면, Event Handler는 렌더링 과정에서 발생하는게 아니라, 렌더링 이후에 특정 행동에 의해서 발생한다. 이는 선언형이 아닌 명령형이라고 할 수 있다. 따라서 Error boundaries가 아닌, try catch로 에러를 핸들링하는 것이 맞다고 할 수 있다. 아래의 예시로 try catch에서 사용되는 Event Handler에 대한 이해를 높여 보자.(공식문서 예시)

 

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    try {
      // Do something that could throw
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <button onClick={this.handleClick}>Click Me</button>
  }
}

 

보면 클릭 handleClick 부분에서 try catch 문이 활용 되었고, error 발생 시 상태를 state에 저장하는 것을 알 수 있다. 사실 이러한 방식은 우리가 자주 사용하는 방식이다. react와 같은 선언형에서는 아래에서 소개할 Error boundaries를 사용한다.

 

 

 

2. Error boundaries Lifecycle method

우선 React에서 제공하는 Error boundaries에 대한 코드를 보자. 

 

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }

  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    this.setState({
      error: error,
      errorInfo: errorInfo,
    });
    // You can also log error messages to an error reporting service here
  }

  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: "pre-wrap" }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }
}

 

코드를 보면 사실 단순하다. compojnentDidCatch method에서 error와 errorInfo 받아서 state 상태로 넣어주긴만 하면 된다. Error boundary에서 사용되고 있는 method들은 위의 예시에서 볼 수 있는 componentDidCatch 뿐만 아니라, getDerivedStateFromError 등등이 있다. 관련 method들은 아래에서 하나씩 확인해 보도록 하자.

 

추가로 현재 frontend의 대세는 class가 아니라 함수형이다. 그런데 왜 Error boundaries는 함수가 아닌 클래스일까? 왜냐하면 아래의 method들이 아직 React 진형에서 Hook을 만들지 않았기 때문이다. 조금 기다려보면, hook으로 나올 것으로 기대된다.

 

 

 

componentDidCatch()

이 lifecycle method는 하위 컴포넌트에서 에러가 발생 시 아래의 두개의 인자를 받는다.

 

componentDidCatch(error, info)

 

error : 발생된 error로 try catch의 error와 같다.

info : error가 발생한 컴포넌트에 대한 정보와 componentStack key가 포함된 객체이다. componentStack이라는 것은 쉽게 말해서 어느 component에서 error가 발생하는지 표현하는 것이다. 예를 들면, react의 경우는 진입점인 index.js부터 app.js 와 같이 발생한 컴포넌트를 추적해서 알려준다.

 

정리하면, componentDidCatch의 첫번째 인자는 error의 내용을 나타내고, 두 번째 인자는 error의 위치를 나타낸다.

 

 

 

getDerivedStateFromError()

이 lifecycle method는 하위 자손 컴포넌트 오류가 발생 했을 때 호출된다. 특히 변화된 state값을 반드시 반환해야 한다. 아래가 예시 코드이다.

 

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

  static getDerivedStateFromError(error) {
    // state를 갱신하여 다음 렌더링에서 대체 UI를 표시합니다.
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // 별도로 작성한 대체 UI를 렌더링할 수도 있습니다.
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children;
  }
}

 

getDerivedStateFromError와 componentDidCatch의 차이점

getDerivedStateFromError()는 기본적으로 render 단계에서 호출되기 때문에 side effect를 발생시키면 안된다.  side effect와 관련된 호출은 componentDidCatch()를 사용한다. 따라서 error 로그 기록을 호출하고 싶다면, getDerivedStateFromError가 아닌 componentDidCatch에서 사용하여 log를 남기면 된다.

 

 

Error boundary 사용

기본적으로 이 글을 읽는 사람들은 Error boundary 사용법에 대해 이해를 하기위해 이 글을 읽고 있을 것이다. 그리고 에러가 발생하면, 전체 에러발생으로 에러화면이 발생하면 안 된다. 특정 화면만 에러상황에 따라 화면이 내가 의도한 데로 변화하게 도움을 주는 것이 Error boundary라고 할 수 있다. 예를 들어 웹툰을 보고 있다. 특정 웹툰에서만 데이터를 못 들고 왔다면, 특정 웹툰이 표시되는 컴포넌트에서만 재시도 버튼을 누를 수 있게 사용자를 유도하면 된다. 아래의 간단한 사용법을 보자.

 

function App() {
  return (
    <div>
      <p>hanpy ErrorBoundary TEST</p>
      <ErrorBoundary>
        <아무컴포넌트1 />
      </ErrorBoundary>
      <ErrorBoundary>
        <아무컴포넌트2 />
      </ErrorBoundary>
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

 

<아무컴포넌트1 /> 과 <아무컴포넌트2 />에서 error가 발생하면, 화면 전체가 error가 발생하는 게 아니라 나의 의도처럼, 에러가 발생한 컴포넌트의 UI만 변경이 가능하다.

 

사실 ErrorBoundary를 커스텀 하는 방법은 더욱 많다. react-query 결합하기, API 비동기 (async) 추가, 컴포넌트 주입, error reset, SSR render 제외하기 등등 다양하게 활용하는 방법에 대해서는 추후 작성 될 Error boundary 심화 글에서 상황에 따른, 다양한 예시로 적어보겠다.

 

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함