본문 바로가기
React

[React] Hook 함수형 컴포넌트

by teamnova 2025. 6. 30.
728x90

안녕하세요!

리액트를 통해 개발을 하다 보면 useState, useEffect 같은 단어들을 자주 접하셨을 텐데요,

이들은 모두 '훅(Hook)'이라는 리액트의 기능을 담당하는 함수들입니다.

 

훅(Hook)이 등장하기 전: 클래스 컴포넌트의 복잡함

훅이 등장하기 전, 리액트에서 '상태(state)'를 관리하거나 '생명주기(lifecycle)' 기능을 사용하려면 반드시 클래스 컴포넌트(Class Component) 사용해야 했습니다.

 

import React, { Component } from 'react';

class CounterClass extends Component {
  constructor(props) {
    super(props);
    // state는 constructor 안에서 this.state로 초기화
    this.state = {
      count: 0
    };
  }

  // componentDidMount: 컴포넌트가 처음 마운트된 후 실행
  componentDidMount() {
    document.title = `클릭 수: ${this.state.count}`;
  }
 
  // componentDidUpdate: 컴포넌트가 업데이트된 후 실행
  componentDidUpdate() {
    document.title = `클릭 수: ${this.state.count}`;
  }

  // 이벤트 핸들러에서 state 변경은 this.setState 사용
  handleCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>클릭 수: {this.state.count}</p>
        <button onClick={this.handleCount}>
          클릭하세요
        </button>
      </div>
    );
  }
}

 

해당 클래스 컴포넌트는 아래와 같은 단점을 가지고 있습니다.

 

1. 많은 코드 사용 : constructor, super(props), this.state, this.setState, this.handleCount 등 불필요해 보이는 코드가 많았습니다.

2. this 키워드의 사용: 자바스크립트의 this 바인딩 문제로 인해 개발자들이 어려움을 겪었습니다.
3. 로직 재사용의 어려움: 여러 컴포넌트에서 동일한 로직(예: API 호출, 구독)을 재사용하기 위해 HOC(Higher-Order Components)나 Render Props 패턴을 사용해야 했는데, 이는 코드의 중첩을 심화시켜 가독성을 해치기도 했습니다.

 

그럼 Hook 이란 무엇일까요?

 

함수형 컴포넌트 안에서 state와 생명주기(lifecycle) 같은 리액트의 기능들을 사용할 수 있게 해주는 함수들의 모음입니다.

위의 설명된 글 내용처럼 복잡함과 어려움을 해결하기 위해 리액트 16.8 버전부터 hook이 도입되었습니다.

 

Hook을 사용하기 위해서는 지켜야할 규칙이 있습니다.

 

1. 최상위 레벨에서만 훅 호출: if 문, for 반복문, while 반복문, 중첩된 함수 내부에서는 훅을 호출하면 안 됩니다. 항상 컴포넌트 함수의 가장 바깥쪽(최상위 레벨)에서만 호출해야 합니다.

 

// ❌ 잘못된 사용 예시
function MyComponent({ condition }) {
  if (condition) {
    const [value, setValue] = useState(''); // if문 안에서 useState 호출
  }
  // ...
}

// ✅ 올바른 사용 예시
function MyComponent({ condition }) {
  const [value, setValue] = useState(''); // 항상 최상위 레벨에서 useState 호출
  if (condition) {
    // ... 훅이 아닌 일반적인 로직은 조건문 안에 있어도 괜찮습니다.
  }
  // ...
}

 

 

2. 리액트 함수에서만 훅 호출: 훅은 리액트 함수형 컴포넌트나 커스텀 훅(Custom Hook) 안에서만 호출해야 합니다. 

일반 자바스크립트 함수 안에서는 훅이 작동하지 않습니다.

 

// ✅ 올바른 사용 예시
function MyComponent() {
  const [count, setCount] = useState(0); // 컴포넌트 안에서 useState 호출
  // ...
}

// ❌ 잘못된 사용 예시 (일반 JS 함수)
function myUtilityFunction() {
  const [count, setCount] = useState(0); // 컴포넌트 밖에서 useState 호출
}

 

대표적인 기본 훅 예제

useState - 컴포넌트의 상태 관리

import React, { useState } from 'react';

function Counter() {
  // 1. useState(초기값)를 호출하여 상태 변수와 상태 변경 함수를 얻습니다.
  //    [현재상태값, 상태를변경하는함수] 형태의 배열을 반환합니다.
  const [count, setCount] = useState(0); // count라는 상태는 0으로 초기화됩니다.

  const increment = () => {
    // setCount 함수를 사용하여 상태를 업데이트합니다.
    // 이전 상태값을 기반으로 새로운 상태 값을 계산할 때는 함수 형태로 전달하는 것이 안전합니다.
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <p>현재 숫자: {count}</p>
      <button onClick={increment}>
        숫자 증가시키기
      </button>
    </div>
  );
}

export default Counter;

 

useState(0)를 호출하여 count라는 상태 변수를 만들고, 초기값을 0으로 설정했습니다. setCount 함수를 호출하면 count 상태가 업데이트되고, 리액트는 자동으로 컴포넌트를 다시 렌더링하여 변경된 count 값을 화면에 보여줍니다.

 

예제:

 

useEffect - 부수 효과(Side Effect) 처리

useEffect는 컴포넌트가 렌더링된 후 또는 특정 상태가 변경된 후에 어떤 작업을 수행하도록 설정할 때 사용합니다. 흔히 클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount 생명주기 메서드의 역할을 통합하여 처리합니다.

 

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

function DocumentTitleChanger() {
  const [count, setCount] = useState(0);

  // 1. useEffect(콜백함수, 의존성배열)
  //    콜백함수: 컴포넌트가 렌더링될 때마다 실행될 로직
  //    의존성배열: 이 배열 안의 값이 변경될 때마다 콜백함수를 다시 실행합니다.
  //               빈 배열([])을 전달하면 컴포넌트가 처음 마운트될 때 딱 한 번만 실행됩니다.
  useEffect(() => {
    // 컴포넌트가 렌더링된 후에 실행될 로직
    document.title = `클릭 횟수: ${count}`;
    console.log('useEffect 실행됨!');

    // 클린업 함수: 컴포넌트가 언마운트되거나 effect가 다시 실행되기 전에 실행될 로직
    // 주로 이벤트 리스너 제거, 구독 해제 등에 사용됩니다.
    return () => {
      console.log('cleanup 실행됨!');
      // 예시: document.title을 이전 상태로 복구하거나 불필요한 작업을 정리할 수 있습니다.
    };
  }, [count]); // count 값이 변경될 때마다 effect를 다시 실행하도록 의존성 배열에 count 추가

  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <p>클릭 횟수: {count}</p>
      <button onClick={increment}>
        클릭하여 카운트 증가
      </button>
    </div>
  );
}

export default DocumentTitleChanger;

 

예제

 

useEffect(() => { ... }, [count])는 count 값이 변경될 때마다 콜백 함수 내의 document.title을 업데이트합니다.
콜백 함수 안에서 return () => { ... } 형태로 **클린업 함수(cleanup function)**를 반환할 수 있습니다. 이 함수는 컴포넌트가 언마운트될 때나, useEffect의 의존성 배열에 있는 값이 변경되어 effect가 다시 실행되기 전에 호출됩니다. 이벤트 리스너 제거 등에 유용합니다.
만약 의존성 배열을 빈 배열 []로 전달하면, useEffect는 컴포넌트가 처음 마운트될 때 딱 한 번만 실행되고 그 이후로는 실행되지 않습니다. (클래스 컴포넌트의 componentDidMount와 유사한 용도)