React 무료한 삶 #17

CoderS·2021년 12월 22일
0

리액트 Remind

목록 보기
17/32

#17 하루가 너무 빠르다!?

componentDidCatch / Sentry 연동

저번에 이어서 오늘은 componentDidCatch 라는 생명주기 메서드를 사용해서 리액트 애플리케이션에서 발생하는 에러를 처리하는 방법을 알아보도록 하겠다.

이 튜토리얼을 진행하기 앞서 새로운 프로젝트를 생성해준다.

$ npx create-react-app error-catch

다 생성이 완료되면, 해당 디렉토리를 열고 개발 서버를 시작해준다.

리액트 앱에서 에러가 발생하는 상황

User.js 라는 파일을 src 디렉터리에 생성하여 다음과 같이 컴포넌트 코드를 작성해준다.

User.js

import React from 'react';

function User({ user }) {
  return (
    <div>
      <div>
        <b>ID</b>: {user.id}
      </div>
      <div>
        <b>Username:</b> {user.username}
      </div>
    </div>
  );
}

export default User;

이 컴포넌트는 user 라는 props 로 받아와서 데이터의 id 와 username 값을 보여준다.

이제 App 컴포넌트에서 렌더링해서 사용해보겠다.

App.js

import React from 'react';
import User from './User';

function App() {
  const user = {
    id: 1,
    username: 'velopert'
  };
  return <User user={user} />;
}

export default App;

위에처럼 결과가 나타날 것 이다. 하지만 만약에 user props 를 제대로 설정하지 않았다면 어떤 결과를 초래할까?

App.js

import React from 'react';
import User from './User';

function App() {
  const user = {
    id: 1,
    username: 'velopert'
  };
  return <User />;
}

export default App;

이렇게 에러가 발생할 것 이다.

실제 환경에서는 아무것도 렌더링되지 않고 흰 페이지만 나타나게 된다.

이번 튜토리얼에서는 이런 상황에 이렇게 흰 화면을 보여주는 대신에, 에러가 발생했다는 것을 알려주는 방법에 대하여 알아볼 것 이다.

진행하기 앞서 어떤 상황에 이런 에러가 발생하는지 알아보고, 에러를 방지할 수 있는 방법에 대해 알아보겠다.

방금과 같은 에러를 방지하려면 User 컴포넌트에서 다음과 같은 작업을 하면된다.

User.js

import React from 'react';

function User({ user }) {
  if (!user) {
    return null;
  }

  return (
    <div>
      <div>
        <b>ID</b>: {user.id}
      </div>
      <div>
        <b>Username:</b> {user.username}
      </div>
    </div>
  );
}

export default User;

이렇게 해서 user 값이 존재하지 않는다면 null 을 렌더링해준다. 리액트에서는 null 을 렌더링하면 아무것도 나타나지 않는다. 이것을 'null checking' 이라고 부른다.

이렇게 코드를 작성하면 적어도 에러는 발생하지 않는다.

보통 데이터를 네트워크 요청을 통해서 나중에 데이터를 받아오게 되는 상황이 발생하면 이렇게 데이터가 없으면 null 을 보여주거나 아니면...

<div>로딩중</div>

와 같은 결과물을 렌더링하면 된다.


다른 에러가 발생할 수 있는 상황에 알아보자!

function Users({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.username}</li>
      ))}
    </ul>
  );
}

만약에 위와 같은 컴포넌트에 users 값을 설정해주지 않았을 때에도 렌더링 과정에서 오류가 발생하게 된다.

users 가 undefined 이면 당연히 배열의 내장함수 map 또한 존재하지 않는다.

그리하여 위의 경우처럼 users 가 없다면 다른 결과물을 반환하는 작업을 하면 된다.

function Users({ users }) {
  if (!users) return null;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.username}</li>
      ))}
    </ul>
  );
}

또 다른 상황으로는...

function Users({ users, onToggle }) {
  if (!users) return null;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id} onClick={() => onToggle(user.id)}>
          {user.username}
        </li>
      ))}
    </ul>
  );
}

만약에 위 컴포넌트에 onToggle props 을 전달하지 않는다면, 에러가 발생할 것 이다. 에러를 방지하기 위해서 onToggle 에다가 props 로 넣어주는 것을 까먹지 않기 위해 다음과 같이 defaultProps 을 설정을 해주는 방법이 있다.

function Users({ users, onToggle }) {
  if (!users) return null;

  return (
    <ul>
      {users.map(user => (
        <li key={user.id} onClick={() => onToggle(user.id)}>
          {user.username}
        </li>
      ))}
    </ul>
  );
}

Users.defaultProps = {
  onToggle: () => {
    console.warn('onToggle is missing!');
  }
};

defaultProps 가 싫다면 다른 솔루션으로 PropTypes 라는 것을 사용하는 방법이 있다.

PropTypes 를 사용하면 필요한 데이터를 넣지 않았을 때 개발 단계에서 경고를 볼 수 있기 때문에 실수로 props 설정을 깜박하는 일을 방지 할 수 있다.

componentDidCatch 로 에러 잡기

이번에는 componentDidCatch 생명주기 메서드를 사용해서 우리가 사전에 예외처리를 하지 않은 에러가 발생했을 때 사용자에게 에러가 발생했다다고 알려주는 화면을 보여주겠다.

우선 src 디렉토리에 ErrorBoundary 라는 컴포넌트를 만들어보자!

ErrorBoundary.js

import React, { Component } from 'react';

class ErrorBoundary extends Component {
  state = {
    error: false
  };

  componentDidCatch(error, info) {
    console.log('에러가 발생했습니다.');
    console.log({
      error,
      info
    });
    this.setState({
      error: true
    });
  }

  render() {
    if (this.state.error) {
      return <h1>에러 발생!</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

여기서 componentDidCatch 메서드에서 두 개의 파라미터를 사용하게 되는데 첫 번째는 에러의 내용, 두 번째 파라미터는 에러가 발생한 위치를 알려준다.

이 메서드에서 현재 컴포넌트 상태 error 를 true 로 설정을 해주고, render( ) 메서드에서는 만약 this.state.error 값이 true 라면 에러가 발생했다는 문구를 렌더링하도록 하고 그렇지 않다면 this.props.children 을 렌더링하도록 처리해준다.

다음으로 App 컴포넌트에 있는 User 컴포넌트를 감싸준다.

App.js

import React from 'react';
import User from './User';
import ErrorBoundary from './ErrorBoundary';

function App() {
  const user = {
    id: 1,
    username: 'velopert'
  };
  return (
    <ErrorBoundary>
      <User />
    </ErrorBoundary>
  );
}

export default App;

그리고 이전에 User 컴포넌트에서 null checking 부분을 주석처리해준다.

User.js

import React from 'react';

function User({ user }) {
  // if (!user) {
  //   return null;
  // }

  return (
    <div>
      <div>
        <b>ID</b>: {user.id}
      </div>
      <div>
        <b>Username:</b> {user.username}
      </div>
    </div>
  );
}

export default User;

브라우저를 확인해보면 흰 화면이 아닌 "에러 발생!" 이라는 문구가 보여지게 될 것이다.

Sentry 연동

componentDidCatch 를 사용해서 앱에서 에러가 발생했을 때 사용자에게 에러가 발생했음을 인지시켜줄 수는 있지만, componentDidCatch 가 실제로 호출되는 일은 서비스에서 "없어야 하는게" 맞다.

만약에 우리가 놓친 에러가 존재한다면, 이를 알아내어 예외 처리를 해주어야 한다.

가끔 우리가 발견하지 못하고, 사용자가 발견하게 되는 오류를 종종 봤을것이다. 그럴 때는 componentDidCatch 에서 error 와 info 값을 네트워크를 통하여 다른 곳으로 전달을 해주면 된다.

하지만 이를 위해서 따로 서버를 만드는 건 굉장히 번거로운 작업이다. 여기서 정말로 괜찮은 솔루션으로 Sentry 라는 상용서비스가 있다.

물론 돈을 내고 더 많은 작업을 할 수 있지만, 무료로도 충분히 사용할 수 있어 장기적으로 작업하는 프로젝트에 안성맞춤이다.

https://sentry.io

위의 링크로가서 회원가입 및 로그인을 하고 새로운 프로젝트를 생성해준다. 회원가입 과정에서 속해있는 팀이 없다면 아무거나 자유롭게 입력해도 된다.

여기서 우리는 React 를 선택해준다.

다음으로 프로젝트 생성하는 과정이 있는데...

우리가 아까 만든 프로젝트에 한번 적용을 해보겠다.

프로젝트 디렉토리에 밑에 있는 커맨드를 설치하라!

npm install --save @sentry/react @sentry/tracing

설치가 완료되면 Sentry 페이지에서 나타났던 Instruction 에 나타났던 대로 작업을 해주면된다.

import React from "react";
import ReactDOM from "react-dom";
import * as Sentry from "@sentry/react";
import { Integrations } from "@sentry/tracing";
import App from "./App";

Sentry.init({
  dsn: "https://f7a5f21f11874a41a3fa1bd36cbe7f64@o1097147.ingest.sentry.io/6118393",
  integrations: [new Integrations.BrowserTracing()],

  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for performance monitoring.
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,
});

ReactDOM.render(<App />, document.getElementById("root"));

// Can also use with React Concurrent Mode
// ReactDOM.createRoot(document.getElementById('root')).render(<App />);

위의 코드를 우리의 프로젝트 index.js 파일에 복사해서 붙여넣는다.

완료가되면...

사이트 밑으로 내려가서 빨간줄로 그어져있는 버튼을 클릭해준다.

그리고 Issues 로 가서...

이렇게 실시간으로 확인이 가능하다.

그런데 여기서 끝이 아니다. 이렇게 에러가 발생했을 때, Sentry 쪽으로 전달이 되는 것은 개발모드일땐 별도의 작업을 하지 않아도 잘 작동하지만, 나중에 프로젝트를 완성하여 실제 배포를 하게 됐을 때는 componentDidCatch 로 이미 에러를 잡아줬을 경우 Sentry 에게 자동으로 전달이 되지 않는다.

때문에 ErrorBoundary 에서 다음과 같이 처리를 해주셔야 한다.

ErrorBoundary.js

import React, { Component } from 'react';
import * as Sentry from "@sentry/react";

class ErrorBoundary extends Component {
  state = {
    error: false
  };

  componentDidCatch(error, info) {
    console.log('에러가 발생했습니다.');
    console.log({
      error,
      info
    });
    this.setState({
      error: true
    });
    if (process.env.NODE_ENV === 'production') {
        Sentry.captureException(error, { extra: info });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>에러 발생!</h1>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

componentDidCatch 에서 process.env.NODE_ENV 값을 조회를하고, 이를 통하여 현재 환경이 개발 환경인지 프로덕션 환경인지 (production / development) 확인 할 수 있다.

프로덕션 환경에서 잘 작동하는지 확인하기

프로덕션 환경에서도 잘 작동하는지 확인하기 위해서는 프로젝트를 빌드해주어야 합니다. 프로젝트 디렉터리에서 다음처럼 명령어를 실행해준다.

$npm run build

기다리면 build 라는 디렉토리가 생성되어있다. build 디렉터리에 있는 파일들을 제공하는 서버를 실행하기 위해서는 다음 명령어를 실행해준다.

$ npx serve ./build

이 때, serve 는 웹 서버를 열어서 특정 디렉토리에 있는 파일을 제공해주는 도구이다.

http://localhost:3000/

해당 로컬 서버를 열어준 뒤, Sentry 에 새로운 항목이 추가됐는지 확인해보자!

이번에는 아까와 달리 에러가 어디에서 발생했는지 정보를 알아보기 어렵다.

이는 빌드 과정에서 코드가 minify 되면서 이름이 c, Xo, Ui, qa 이런식으로 축소됐기 때문이다.

Sentry 에서 minified 되지 않은 이름을 보려면 Sourcemap 이란것을 사용해야 하는데, 빌드를 할 때마다 자동으로 업로드 되도록 설정 할 수 있고, 직접 업로드 할 수도 있고, 만약에 Sourcemap 파일이 공개 되어 있다면 별도의 설정 없이 바로 minified 되지 않은 이름을 볼 수 있다.

참고 : 벨로퍼트와 함께하는 모던 리액트

느낀점 :

  • 오늘은 componentDidCatch 의 사용법을 알아보고, Sentry 와 연동하는 방법도 알아보았다.
  • Sentry 를 연동하는 작업은 필수는 아니겠지만, 장기간 프로젝트에서 에러가 발생하면 찾기 힘드니 버그를 관리할 때 매우 편리한 기능이겠다.
profile
하루를 의미있게 살자!

0개의 댓글