React를 처음 배우면서 다양한 개념이 마구마구 머릿속으로 들어오고 있는데,, Hook의 다양한 종류와 사용법에 대해서 배우면서 갑자기 많은 개념에 조금 당황했던 것 같다.
그래서 이번 시간에는 React의 Hook에 대해 언제, 왜, 어떻게 사용하는지를 입문자 입장에서 좀 더 쉽게 정리해보려고 한다!


1️⃣Hook이란?

Hook은 React 함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 관련 기능을 사용할 수 있도록 도와주는 함수이다.
원래 상태 관리와 생명주기 메서드는 클래스형 컴포넌트에서만 가능했지만, React의 Hook을 사용하면 함수형 컴포넌트에서도 이를 구현할 수 있다!

🤔 Hook은 왜 사용할까?

  • 코드가 간결해지고 가독성이 좋아진다
    • 클래스형 컴포넌트에서는 this.state, this.setState() 등을 사용해야 하지만, Hook을 사용하면 useState로 간단하게 관리가 가능하다
  • 재사용성이 높아진다
    • 여러 컴포넌트에서 공통으로 필요한 로직을 Custom Hook으로 분리해 재사용이 가능하다.
  • 클래스형 컴포넌트의 복잡한 로직을 단순화할 수 있다.
    • 클래스형 컴포넌트에서는 componentDidMount, componentDidUpdate, componentWillUnmoun 등의 생명주기 메서드를 사용해야 하지만, Hook을 사용하면 useEffect 하나로 처리 가능하다

🤔 어떻게 사용할까?

  • React에서 기본 제공하는 Hook을 활용해 상태 관리, 부수 효과 관리, 성능 최적화 등을 할 수 있다.
  • 대표적 Hook으로는 useState, useEffect, useRef, useMemo, useCallback 등이 있으며, 필요에 따라 Custom Hook을 직접 만들어서 활용할 수도 있다.
  • Hook 사용 시 반드시 React Hook의 규칙을 지켜야 한다!
    • Hook은 최상위 레벨에서만 호출해야하기 때문에, 조건문이나 반복문 안에서는 사용할 수 없다.

2️⃣React Hook 종류와 사용법

🔴 useState

  • 컴포넌트에서 상태(state)를 관리할 수 있게 해주는 Hook

언제?

  • 컴포넌트에서 값이 변경될 때마다 화면을 다시 렌더링해야 할 때
  • 사용자 입력, 버튼 클릭 등의 이벤트로 상태를 변경해야 할 때

왜?

  • 함수형 컴포넌트에서는 기본적으로 상태를 저장할 방법이 없기 때문
  • useState를 사용하면 간단하게 상태를 선언하고 변경할 수 있음

어떻게?

⚙️ 동작 방식
useState초기값을 전달하면 상태 변수와 그 값을 업데이트할 수 있는 함수를 반환한다.
상태값은 컴포넌트의 렌더링 사이에서 기억(저장)되며, 상태가 변경되면 컴포넌트는 다시 랜더링된다.
이 과정은 상태가 바뀌었을 때만 화면을 업데이트하여 최적화 렌더링을 보장한다

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0); // count 상태를 0으로 초기화

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>Click me</button> 
    </div>
  );
}

✏️ 코드 설명

  • const [count, setCount] = useState(0);
    • useState는 배열을 반환하며 첫 번째 요소는 현재 상태 값 (count), 두 번째 요소는 그 값을 업데이트하는 함수(setCount)이다
  • <button onClick={() => setCount(count + 1)}>Click me</button>
    • setCount 함수는 상태를 직접 변경하는 것이 아니라, 상태 업데이트 요청을 React에 전달한다.
    • 상태가 변경되면 React는 다시 랜더링 하며,count값이 업데이트된 화면을 보여준다.

🟠useEffect

  • 컴포넌트의 부수 효과(side effect)를 처리할 수 있게 해주는 Hook

🤔 부수 효과(side effect)란?

  • 컴포넌트의 렌더링 외부에서 발생하는 모든 작업을 의미
  • 예를 들어, API 요청, DOM 조작, 타이머 설정, 로컬 스토리지 저장 등은 렌더링 동안 바로 실행 될 수 없다.
  • 주로 컴포넌트의 상태나 렌더링을 직접 변경하지 않지만, 그 결과로 다른 컴포넌트나 시스템에 영향을 미치는 작업들이다.
  • React에서 useEffect는 이러한 부수 효과를 컴포넌트의 렌더링 후에 처리하게 해주며, 렌더링 중에는 실행되지 않도록 보장한다.

언제?

  • 컴포넌트가 처음 렌더링될 때나 특정 값이 변경될 때 실행해야 할 코드가 있을 때
  • API 호출, 구독(subscriptipn), 이벤트 리스너 등록/ 해제 등이 필요할 때

왜?

  • React의 렌더링 흐름에서 특정 시점( 마운트, 업데이트, 언마운트 )에 실행할 코드를 지정할 방법이 필요하기 때문
  • 클래스형 컴포넌트의 생명주기 메서드를 대체할 수 있음

어떻게?

⚙️ 동작 방식

  • useEffect(이펙트 함수, 의존성 배열);
  • 렌더링 이후에 실행되며, 두 번째 인수로 의존성 배열을 전달하여 언제 재실행할지를 제어할 수 있다.
  • 만약 의존성 배열 속 값이 하나라도 변경되면 이펙트 함수가 실행된다.
import { useState, useEffect } from "react";

function Timer() {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const timer = setInterval(() => {
      setTime(prevTime => prevTime + 1);
    }, 1000);

    return () => clearInterval(timer); //컴포넌트가 사라질 때 인터벌 정리
  }, []);

  return <p>현재 시간: {time}</p>;
}

✏️ 코드 설명

  • useEffect(() => {...}, []);
    • 첫 번째 인자로 전달된 함수는 컴포넌트가 렌더링된 이후에 실행된다. 위 코드에서는 타이머를 설정하고, 시간이 1초마다 증가하는 효과를 만든다.
    • 두 번째 인자 []은 빈 배열로 설정되어 있기 때문에, 컴포넌트가 처음 마운트될 때만 실행된다. (최초 1회만 실행)
  • return () => clearInterval(timer);
    • 컴포넌트가 언마운트될 때 타이머를 정리(cleanup)하는 함수이다. 이때 clearInterval을 호출해서 타이머가 계속 실행되지 않도록 한다.

🟡useMemo

  • 값을 기억해서, 의존성 배열이 변경되지 않는 한 불필요한 계산을 피할 수 있게 해주는 Hook

언제?

  • 계산 비용이 높은 연산이 있을 때
  • 상태가 변경될 때마다 불필요한 연산을 피하고 싶을 때

왜?

  • 매 렌더링마다 동일한 연산이 반복되는 것을 방지해서 성능 최적화 할 수 있음
  • useMemo를 사용하면 특정 값이 변경될 때만 연산을 실행하도록 할 수 있음

어떻게?

⚙️ 동작 방식

  • 함수가 비싼 계산을 반복하지 않도록 결과를 메모이제이션한다.
  • 의존성 배열에 포함된 값이 변경될 때만 계산이 다시 실행되며, 나머지 경우엔 이전에 계산된 값을 재사용한다.
import { useMemo } from "react";

function ExpensiveComputation({ num }) {
  const result = useMemo(() => {
    console.log("계산 중...");
    return num * 2;  // 비싼 계산을 하는 코드
  }, [num]);  // num이 변경될 때만 재계산

  return <p>결과: {result}</p>;
}

✏️ 코드 설명

  • const result = useMemo(() => { ... }, [num]);
    • num 값이 변경될 때만 계산하고, 그 외의 경우에는 기존 값을 재사용한다.
    • 따라서, console.log("계산 중...");num이 변경될 때만 출력한다.

🟢useCallback

  • 함수가 다시 생성되는 것을 방지하여 불필요한 리렌더링을 줄여주는 Hook
  • useMemo와 비슷하지만 useMemo는 값을 반환하고, useCallback은 함수를 반환한다.

언제?

  • 함수가 불필요하게 재생성되는 것을 방지하고 싶을 때
  • 자식 컴포넌트에 함수를 props로 전달할 때

왜?

  • 매 렌더링마다 함수가 새로 생성되면 불필요한 리렌더링이 발생할 수 있음
  • useCallback을 사용하면 기존 함수를 재사용하여 성능을 향상시킬 수 있음

어떻게?

⚙️ 동작 방식

  • useCallback은 함수를 메모이제이션 하여 동일한 함수 객체를 유지
  • 두 번째 인자로 주어진 의존성 배열의 값이 변경될 때만 새로운 함수 생성
  • 함수가 필요할 때마다 새로 생성되지 않고, 이전 함수를 재사용하여 불필요한 리렌더링 방지
import { useState, useCallback } from "react";

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

  const handleClick = useCallback(() => {
    console.log(count);
  }, [count]);  // count가 변경될 때만 함수 재생성

  return <Child onClick={handleClick} />;
}

function Child({ onClick }) {
  return <button onClick={onClick}>Click</button>;
}

✏️ 코드 설명

  • const handleClick = useCallback(() => {...}, [count]);
    • count가 변경될 때만 함수를 재생성하고, 그 외의 경우에는 기존 함수를 재사용
    • 따라서, console.log(count);count가 변경될 때만 출력한다.

🔵useRef

  • 렌더링 없이 값을 유지할 수 있는 변경 가능한 객체(Ref)를 생성하는 Hook
  • DOM 요소에 직접 접근할 때도 사용 가능

언제?

  • 렌더링과 상관없이 값을 저장해야 할 때
  • 특정 DOM 요소 (input, button 등)에 직접 접근해야 할 때
  • 이전 값을 저장해야 할 때

왜?

  • useState를 사용하면 값이 변경될 때마다 리렌더링이 발생하지만, useRef는 렌더링 없이 값을 유지할 수 있기 때문
  • 특정 DOM 요소에 접근하기 위해서

어떻게?

⚙️ 동작 방식

  • .current속성을 가진 객체를 반환
  • useRef의 값은 컴포넌트가 리렌더링되어도 유지되며, 값이 변경되어도 리렌더링이 발생하지 않음
import { useRef } from "react";

function InputFocus() {
  const inputRef = useRef();

  const handleClick = () => {
    // inputRef.current는 DOM 요소를 가리키므로 focus() 호출 가능
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={handleClick}>Input Focus</button>
    </div>
  );
}

✏️ 코드 설명

  • useRef는 DOM 요소를 직접 참조하거나, 렌더링 중 변경될 수 있는 값을 저장하는 데 사용된다.
  • 렌더링 간에 유지되므로, 값 변경 시 컴포넌트가 다시 렌더링되지 않는다.
  • inputRef.current처럼 DOM을 직접 조작할 때 유용하게 쓰인다.

🔥Hook 총정리

Hook역할특징언제 사용?
useState상태 관리값이 변경되면 리렌더링 발생입력 값 관리, 버튼 클릭 시 값 변경
useEffect부수 효과 처리렌더링 이후 특정 동작 실행API 호출, 이벤트 리스너 등록
useMemo연산 최적화값 재계산 방지복잡한 계산이 필요할 때
useCallback함수 최적화함수 재생성 방지자식 컴포넌트에 함수 전달 시
useRef렌더링 방지, DOM 조작값이 변경되어도 리렌더링 안됨포커스 제어, 이전 값 저장

😶‍🌫️ 마치며...

이번 글을 통해서 Hook을 다시 정리하기 전까지는 각각의 Hook에 대해 개념은 배웠지만, 실제로 어떤 상황에서 어떤 Hook을 적용해야 하는지에 대한 감이 잘 오지 않았다.

하지만, 이번 정리를 통해 각 Hook의 동작 방식과 활용 방법을 보다 체계적으로 정리할 수 있었고, Hook의 사용에 대한 기본적 틀을 잡을 수 있었다.

앞으로 더 익숙해지기 위해서 강의 등을 통해 작은 서비스를 클론 코딩하면서 실전 감각을 키울 계획이다!

이번 글은 나 스스로 Hook을 이해하기에 중점을 두고 쓰다보니 글이 다소 길어진 감이 있다 ㅎ.. 이번 글이 Hook이 헷갈리면 언제든 다시 와서 확인할 수 있는 사전 처럼 활용하면 좋을 것 같다!

profile
아는만큼 보이는🌱👀

0개의 댓글