1008 TIL-U

Lilac00xx·2024년 10월 8일

Section 14 컴포넌트를 구축하는 다른 방법: 클래스 컴포넌트

리액트에서 컴포넌트를 작성하는 방법에는 두 가지가 있다. 함수형 컴포넌트와 클래스 컴포넌트인데, 오늘은 클래스 컴포넌트에 대해 배웠다. 요즘은 함수형 컴포넌트가 주로 사용되지만, 기존 코드베이스나 라이브러리에서는 여전히 클래스 컴포넌트를 많이 볼 수 있다.

TIL1) 1. 함수형 컴포넌트 vs. 클래스 컴포넌트

함수형 컴포넌트는 주로 useState, useEffect 같은 훅을 통해 상태와 사이드 이펙트를 관리. 반면, 클래스 컴포넌트는 상태와 생명주기 메서드를 사용한다.

함수형 컴포넌트: 리액트 훅 사용 가능
클래스 컴포넌트: 리액트 훅 사용 불가, 대신 생명주기 메서드 제공

TIL2) 클래스 컴포넌트 구조

클래스 컴포넌트는 리액트의 Component 클래스를 확장한다. 내부적으로 render() 메서드를 포함하며, 이 메서드에서 UI를 반환한다.

import React, { Component } from 'react';

class MyComponent extends Component {
  render() {
    return <div>Hello, World!</div>;
  }
}

TIL3) State 및 이벤트 관리

클래스 컴포넌트에서 상태를 관리할 때는 this.state를 사용하고, 상태를 업데이트할 때는 this.setState()를 사용함. 함수형 컴포넌트의 useState와 유사한 역할을 하지만, 클래스 기반에서는 좀 더 명시적인 방식으로 사용.

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

TIL4) 컴포넌트 생명주기 메서드

클래스 컴포넌트의 핵심은 생명주기 메서드. 컴포넌트가 마운트될 때, 업데이트될 때, 언마운트될 때 특정 작업을 수행할 수 있다.

componentDidMount(): 컴포넌트가 처음 화면에 나타났을 때 실행됨.
componentDidUpdate(): 컴포넌트가 업데이트될 때마다 호출.
componentWillUnmount(): 컴포넌트가 화면에서 사라지기 직전에 호출.

class MyComponent extends Component {
  componentDidMount() {
    console.log('컴포넌트가 마운트되었습니다.');
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('컴포넌트가 업데이트되었습니다.');
  }

  componentWillUnmount() {
    console.log('컴포넌트가 언마운트됩니다.');
  }

  render() {
    return <div>Lifecycle Methods</div>;
  }
}

TIL5) Error Boundary와 componentDidCatch()

지난주에 들었던 Error Boundary는 리액트 애플리케이션에서 발생한 에러를 잡아내고 처리할 수 있는 방법이다! 이를 위해 클래스 컴포넌트에서 componentDidCatch()라는 생명주기 메서드를 사용할 수 있다. 이 메서드는 하위 트리에서 발생한 JavaScript 에러를 잡아낼 때 사용된다더라.

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

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

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

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

Learned

이번에 클래스 컴포넌트에 대해 학습하면서 느낀 점은, 함수형 컴포넌트가 주류로 자리 잡은 지금에도 여전히 클래스 컴포넌트가 중요한 이유가 있다는 것이다.

과거 프로젝트에서 코드를 분석하다가 클래스 컴포넌트를 처음 접했을 때는 생명주기 메서드의 흐름을 이해하는 것이 다소 어려웠다. 특히, 기술면접대 자주 들어보는 질문중 하나인데, 그냥 외워서만 갔다. But 이번 학습을 통해 클래스 컴포넌트의 생명주기가 컴포넌트의 상태와 동작을 보다 명확하게 제어할 수 있다는 점을 깨달았다. 마치 프로젝트의 백엔드 API 호출 시점이나 화면이 갱신되는 타이밍을 정밀하게 제어할 수 있다는 것이다!

특히 componentDidMount() 메서드를 사용해 초기 데이터를 로드하거나, componentDidUpdate()에서 상태나 속성 변화에 따른 추가 작업을 처리하는 방식이 익숙해지면 매우 효율적이라는 생각이 들었다. 과거 프로젝트에서 주로 함수형 컴포넌트만 사용했기에 상태 변화나 업데이트 로직을 함수 안에서 관리했지만, 클래스 컴포넌트를 접하고 나니 생명주기 메서드를 통해 더 명확하고 체계적으로 관리할 수 있는 장점이 있었다.

이번 학습에서 가장 새로웠던 부분은 Error Boundary를 구현하는 componentDidCatch() 메서드였다. 지난 토욜에, Error Boundary에 대해 들었는데 이게 강의에 나올줄이야. 대박사건,
사용자가 예상치 못한 에러를 만났을 때, 친절한 에러 페이지로 안내하는 경험을 제공하는 것이 얼마나 중요한지 느꼈다. Error Boundary를 통해 클라이언트에서 발생할 수 있는 오류를 잡아내고 이를 사용자에게 알리는 과정이 프로젝트 안정성에 큰 영향을 준다는 점을 이해하게 되었다.

질문거리

componentDidMount()에서 데이터를 로드할 때와 useEffect()에서 데이터를 로드할 때의 차이점은 무엇인가요?

componentDidMount()와 componentDidUpdate() 사이에서 데이터를 갱신해야 하는 상황이 있을 때, 두 메서드 중 어느 곳에서 갱신하는 것이 더 적합한지 판단하는 기준이 있을까요?

클래스 컴포넌트에서 상태와 로직을 재사용할 수 있는 좋은 방법이 있을까요? (ex: 함수형 컴포넌트의 custom hooks처럼 재사용 가능한 패턴)

profile
Challenge & Change

1개의 댓글

comment-user-thumbnail
2024년 10월 16일

사실 클래스 컴포넌트는 이제 React 측에서 공식적으로 사용하지 않는 걸 권장하고 있어서 자세히 알고 계실 필요는 없어요!
하지만 써주신대로 예전 코드나 라이브러리 코드를 볼 때 가끔 접할 때가 있어서 간단하게는 알고 계시면 좋습니다.

  1. useEffect의 dependency array를 빈 array로 작성하면 componentDidMount와 동일하게 작동한다고 보시면 됩니다.

  2. componentDidMount는 컴포넌트가 처음 렌더링될 때 실행되는 메소드로 초기 데이터 로드에 적합하고, componentDidUpdate는 특정 state나 props가 변경될 때 데이터를 갱신해야 한다면 사용하면 좋아요!

  3. 예전에는 보통 HOCs(https://ko.legacy.reactjs.org/docs/higher-order-components.html)를 주로 이용했습니다.

답글 달기