React에서 클래스 컴포넌트와 함수형 컴포넌트의 차이점

이것은 매우 중요한 기술 인터뷰 질문입니다. 표면적으로 보기에는 두 가지 방식으로 컴포넌트를 작성하는 것뿐이지만, 그 차이점은 단순한 문법 차이를 훨씬 넘어섭니다. 아래에서 표면적 차이, 본질적 차이, 그리고 설계 철학 세 가지 측면에서 심층적으로 분석해 보겠습니다.

1. 표면적 차이 (문법 및 기본 사용법)

특성 클래스 컴포넌트 함수형 컴포넌트
문법 ES6 Class, React.Component 상속 JavaScript 함수
상태 this.statethis.setState() 사용 useState Hook 사용
라이프사이클 componentDidMount, componentDidUpdate 등 사용 useEffect Hook 사용
this 키워드 this 바인딩 문제 처리 필요 this 없음
성능 인스턴스 전체로 인해 상대적으로 무거움 가벼움

코드 예시 비교:

// 클래스 컴포넌트
class UserCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { value: 0 };
    // this 바인딩 필요
    this.incrementValue = this.incrementValue.bind(this);
  }

  incrementValue() {
    this.setState({ value: this.state.value + 1 });
  }

  componentDidMount() {
    console.log('컴포넌트 마운트됨');
  }

  render() {
    return <button onClick={this.incrementValue}>값: {this.state.value}</button>;
  }
}

// 함수형 컴포넌트
function FunctionalCounter() {
  const [value, setValue] = useState(0);

  useEffect(() => {
    console.log('컴포넌트 마운트됨');
  }, []);

  const incrementValue = () => {
    setValue(value + 1);
  };

  return <button onClick={incrementValue}>값: {value}</button>;
}

2. 본질적 차이 (근본적인 차이점)

이것이 두 컴포넌트의 차이를 이해하는 핵심입니다.

1. 사고 모델 및 코드 구성

  • 클래스 컴포넌트: 라이프사이클 중심 & 관심사 분리 불량
  • 코드는 "시점"을 중심으로 구성됩니다. "컴포넌트가 마운트된 후(componentDidMount) 무엇을 해야 할까? 업데이트 후(componentDidUpdate) 무엇을 해야 할까? 언마운트 전(componentWillUnmount) 무엇을 해야 할까?"와 같은 고민이 필요합니다.
  • 문제점: 하나의 기능과 관련된 코드(예: 데이터 구독 및 구독 취소)가 다른 라이프사이클 메서드로 분할되며, 관련 없는 코드들은 하나의 메서드에 모여있을 수 있습니다.
class UserProfile extends Component {
  componentDidMount() {
    // 관련 없는 로직 A
    document.title = `현재 점수: ${this.state.score}`;
    // 관련 없는 로직 B
    NotificationAPI.subscribeToUpdates(this.props.userId, this.handleNotification);
  }

  componentDidUpdate(prevProps) {
    // 로직 A의 업데이트 부분
    if (prevProps.score !== this.props.score) {
      document.title = `현재 점수: ${this.state.score}`;
    }
    // 로직 B의 업데이트 부분
    if (prevProps.userId !== this.props.userId) {
      NotificationAPI.unsubscribeFromUpdates(prevProps.userId, this.handleNotification);
      NotificationAPI.subscribeToUpdates(this.props.userId, this.handleNotification);
    }
  }

  componentWillUnmount() {
    // 로직 B의 정리 부분
    NotificationAPI.unsubscribeFromUpdates(this.props.userId, this.handleNotification);
  }
}
  • 함수형 컴포넌트: 상태 중심 & 관심사 분리 우수
  • 코드는 "상태"와 "부수 효과"를 중심으로 구성됩니다. "이 컴포넌트는 어떤 상태에 따라 어떤 부수 효과를 생성해야 할까?"와 같은 고민이 필요합니다.
  • 장점: useEffect를 사용하면 관련 부수 효과의 설정 및 정리 코드를 함께 배치하여 더 나은 관심사 분리를 달성할 수 있습니다.
function UserProfile() {
  const [score, setScore] = useState(0);

  // 로직 A: document.title과 관련된 부수 효과
  useEffect(() => {
    document.title = `현재 점수: ${score}`;
  }, [score]); // score에 의존

  const [userId, setUserId] = useState(null);
  // 로직 B: 알림 구독과 관련된 부수 효과
  useEffect(() => {
    if (userId) {
      NotificationAPI.subscribeToUpdates(userId, handleNotification);
      // 정리 함수와 설정 함수가 함께 배치됨!
      return () => {
        NotificationAPI.unsubscribeFromUpdates(userId, handleNotification);
      };
    }
  }, [userId]); // userId에 의존
}

2. 상태와 클로저

  • 클래스 컴포넌트: 항상 최신 상태에 접근
  • this.statethis.props는 모두 변경 가능하며 항상 최신 값을 가리킵니다. 일부 비동기 작업에서 예기치 않은 결과를 초래할 수 있습니다.
handleButtonClick() {
  setTimeout(() => {
    // 3초 후에 출력되는 값은 클릭 시점의 값이 아닌 최신 score
    console.log(this.state.score); 
  }, 3000);
}
  • 함수형 컴포넌트: 렌더링 시점의 값 캡처
  • 각 렌더링은 독립적인 "스냅샷"이며 자신만의 props, state 및 이벤트 핸들러를 가집니다. 이것이 클로저의 본질입니다.
  • useState의 setter 함수는 안정적이지만, state와 props는 정의된 렌더링 시점에서 캡처됩니다.
function handleButtonClick() {
  const currentScore = score; // 현재 렌더링의 score를 캡처
  setTimeout(() => {
    // 3초 후에 출력되는 값은 여전히 클릭 시점의 score
    console.log(currentScore); 
  }, 3000);
}
  • 최신 값을 읽어야 할 경우 useRef를 사용할 수 있습니다.

3. 로직 재사용

  • 클래스 컴포넌트: 주로 HOC(고차 컴포넌트)Render Props로 구현합니다. "래핑 지옥"(Wrapper Hell)을 유발하기 쉬우며 코드 구조가 복잡해질 수 있습니다.
  • 함수형 컴포넌트: 커스텀 Hook으로 구현합니다. 일반 함수처럼 상태와 부수 효과 로직을 자유롭게 조합할 수 있으며 추가적인 컴포넌트 계층이 없어 코드가 더 명확합니다.

3. 설계 철학과 미래

  • 클래스 컴포넌트: React 초기 ES6 Class를 받아들인 산물로, 더 명령형이며 라이프사이클을 강조합니다.
  • 함수형 컴포넌트 + Hooks: React의 미래로, 더 선언형입니다. 개발자들이 라이프사이클 실행 시점이 아닌 "데이터 흐름"과 "부수 효과"에 더 집중하도록 합니다. React의 함수형 프로그래밍 사상을 더 잘 반영합니다.

요약

차이점 클래스 컴포넌트 함수형 컴포넌트
문법 Class Function
상태/라이프사이클 this.state, 라이프사이클 메서드 Hooks (useState, useEffect)
사고 모델 라이프사이클 중심 상태와 부수 효과 중심
코드 구성 관심사 분리 불량 관심사 분리 우수
상태 캡처 항상 최신 값 렌더링 시점의 값 캡처 (클로저)
로직 재사용 HOC, Render Props 커스텀 Hook
학습 곡선 this와 라이프사이클 이해 필요 클로저와 Hooks 규칙 이해 필요
미래 추세 레거시 주류와 미래

결론: 함수형 컴포넌트 + Hooks는 단순한 문법 단순화를 넘어 사고 모델의 업그레이드입니다. 클래스 컴포넌트가 로직 재사용, 코드 구성 및 이해에 있어 여러 가지 문제점을 해결하며 React의 미래 발전 방향을 대표합니다. 새 프로젝트에는 함수형 컴포넌트 사용을 강력히 권장합니다.

태그: React 컴포넌트 클래스 함수형 Hooks

6월 23일 00:21에 게시됨