React · 2026-02-21

React: 클래스 컴포넌트에서 훅으로 전환하기

클래스 컴포넌트에서 훅으로 마이그레이션하는 핵심 원리와 단계별 예제, 상태·생명주기 변환 방법, 흔한 오류와 성능 고려사항을 정리한 개요

작성일 : 2026-02-21 ㆍ 작성자 : 관리자
post
목차

소개

리액트 생태계에서 클래스 컴포넌트는 오랜 기간 사용되어 왔다. 그러나 훅 도입 이후 함수형 컴포넌트가 주류로 자리잡았다. 이 글은 클래스 -> 함수형 전환 예제와 함께, 왜 전환이 유리한지와 실제 변환 절차를 이해하기 쉽게 정리한다. 또한 'class component to hooks' 관점에서 자주 마주치는 문제와 해결 방향을 다룬다.

왜 훅으로 전환하는가

코드가 더 간결해진다. 상태와 사이드 이펙트를 분리해서 관리하기 쉽다. 메서드 바인딩을 없애 상태 관련 로직의 재사용성이 높아진다. 테스트와 유지보수 측면에서도 이점이 생긴다. 다만 모든 케이스에서 무조건 유리한 것은 아니므로 상황별 판단이 필요하다.

핵심 훅과 대응 관계

useState

클래스의 this.state와 setState를 대체한다. 원시 상태와 복합 상태 모두 useState로 관리 가능하다.

useEffect

componentDidMount, componentDidUpdate, componentWillUnmount의 역할을 하나의 API로 표현한다. 의존성 배열을 통해 실행 시점을 제어한다.

useRef

인스턴스 변수나 DOM 참조를 보관한다. 클래스의 this.someValue와 동일한 목적에 사용된다.

마이그레이션 절차

  • 컴포넌트 구조 파악: state, 라이프사이클, refs, 외부 의존성 확인
  • 상태를 useState로 대체
  • 생명주기 로직을 useEffect로 이동
  • 메서드와 바인딩 제거, 필요한 경우 useCallback 사용
  • refs는 useRef로 변경
  • 컨텍스트 사용 시 useContext 적용
  • 테스트 케이스와 타입 정의 점검

클래스 컴포넌트 예제

아래 예제는 상태, API 호출, 타이머, cleanup을 포함한 간단한 클래스 컴포넌트이다.

import React from 'react';

class TimerComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0, data: null };
    this.handleClick = this.handleClick.bind(this);
    this.timer = null;
  }

  componentDidMount() {
    this.fetchData();
    this.timer = setInterval(() => {
      this.setState(prev => ({ count: prev.count + 1 }));
    }, 1000);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  componentWillUnmount() {
    clearInterval(this.timer);
  }

  fetchData() {
    fetch(`/api/item/${this.props.id}`)
      .then(res => res.json())
      .then(data => this.setState({ data }));
  }

  handleClick() {
    this.setState({ count: 0 });
  }

  render() {
    const { count, data } = this.state;
    return (
      <div>
        <h3>Count: {count}</h3>
        <button onClick={this.handleClick}>Reset</button>
        <pre>{JSON.stringify(data, null, 2)}</pre>
      </div>
    );
  }
}

export default TimerComponent;

훅을 사용한 함수형 컴포넌트 예제

아래는 같은 기능을 useState, useEffect, useRef로 재구성한 코드다. JSX는 < >로 이스케이프 처리됨.

import React, { useState, useEffect, useRef } from 'react';

function TimerComponent({ id }) {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(null);
  const timerRef = useRef(null);

  useEffect(() => {
    let mounted = true;

    fetch(`/api/item/${id}`)
      .then(res => res.json())
      .then(d => {
        if (mounted) setData(d);
      });

    timerRef.current = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);

    return () => {
      mounted = false;
      clearInterval(timerRef.current);
    };
  }, [id]);

  const handleClick = () => setCount(0);

  return (
    <div>
      <h3>Count: {count}</h3>
      <button onClick={handleClick}>Reset</button>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default TimerComponent;

자주 발생하는 실수

  • 의존성 배열 누락으로 인한 무한 루프
  • cleanup을 빼먹어 메모리 누수 발생
  • setState와 비동기 동작의 타이밍 착각
  • 잘못된 ref 사용으로 인한 값 공유 문제

성능과 최적화

렌더링 최적화가 필요한 경우 useCallback과 useMemo 사용을 검토한다. 그러나 과도한 사용은 오히려 복잡도를 높인다. 변경이 자주 발생하는 값에는 상태 분리로 리렌더링 범위를 제한하는 것이 더 효과적이다.

주의사항 및 예외

에러 경계(error boundary)는 현재 함수형 컴포넌트에서 훅만으로 완전히 대체할 수 없다. 이 경우 클래스 기반 에러 경계를 유지하거나 라이브러리 기반 대안을 검토한다. 또한 레거시 라이프사이클 메서드가 복잡한 경우 단계적으로 전환하는 방식이 현실적이다.

마무리

리팩토링 계획은 작고 반복 가능한 단위로 진행하는 것이 안전하다. 핵심은 상태와 사이드 이펙트의 역할을 명확히 분리하는 것이다. '리액트 훅 마이그레이션'은 단순 문법 변경을 넘어 코드 구조 개선의 기회가 된다. 필요 시 기존 클래스와 훅 기반 컴포넌트를 혼용하는 전략도 고려 가능하다.

class component to hooks 리액트 훅 마이그레이션 클래스 -> 함수형 전환 예제 React 훅 useState useEffect useRef 함수형 컴포넌트 리액트 마이그레이션