반응형

리액트 훅 완전정복! 초보자를 위한 주요 훅 사용법 가이드

리액트 초보자라면 꼭 알아야 할 필수 훅들!
직접 써보고 '와, 이게 진짜 편하네' 했던
그 기능들만 모았습니다.
반응형

 

안녕하세요!

리액트를 공부하다 보면 꼭 만나게 되는 개념 중 하나가 바로 "훅(Hook)"이죠. 함수형 컴포넌트 안에서 상태를 관리하거나 생명주기 효과를 처리할 때 빠질 수 없는 친구인데요.

이번 글에서는 useState, useEffect, useRef, useMemo, useCallback, useContext 등 자주 사용되는 훅들을 초보자 눈높이에 맞춰 쉽게 설명해드릴게요.

직접 써보며 헷갈렸던 부분까지 모두 정리했으니 끝까지 함께 해주세요!

 

1. useState 🧠 상태를 다루는 가장 기본 훅

useState는 리액트 훅 중에서 가장 자주 사용되는 기능 중 하나예요.

컴포넌트 안에서 변하는 값, 예를 들면 버튼 클릭 횟수나 입력 필드의 내용 등을 저장하고 관리하는 데 사용됩니다.

기존의 클래스형 컴포넌트에서 this.statethis.setState()를 사용하던 것을 함수형 컴포넌트에서 더 간단하게 구현할 수 있게 해주는 거죠!

📌 기본 문법

const [상태변수, 상태변경함수] = useState(초기값);

🧪 실습 예제

import { useState } from 'react';

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

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

💡 사용 시 팁

  • 여러 개의 상태가 필요한 경우, useState를 여러 번 선언해도 전혀 문제 없어요!
  • 상태가 바뀌면 해당 컴포넌트는 자동으로 다시 렌더링돼요. 이걸 잘 활용해야 렌더링 최적화에 도움이 돼요.

📋 요약 정리

항목 내용
사용 목적 컴포넌트 내 상태값 관리
초기값 숫자, 문자열, 객체 등 다양한 자료형 가능
업데이트 방식 전용 함수(set함수)를 통해 변경

처음 리액트를 접하면 상태라는 개념이 어렵게 느껴질 수도 있지만, useState만 잘 이해해도 리액트 앱의 절반은 완성한 셈이에요!

 

다음은 useEffect를 통해 컴포넌트의 생명주기를 어떻게 다룰 수 있는지 알아볼게요.

 

 

2. useEffect 🔁 생명주기 대체 훅

리액트 함수형 컴포넌트는 생명주기 메서드가 없죠.

그 대신 useEffect를 사용하면 componentDidMount, componentDidUpdate, componentWillUnmount처럼 동작하게 만들 수 있어요.

 

예를 들어,

컴포넌트가 처음 화면에 나타날 때 데이터를 불러온다거나, 특정 값이 바뀔 때마다 API 요청을 보낸다거나, 또는 컴포넌트가 사라질 때 정리 작업(clean-up)을 해야 할 때 아주 유용합니다.

📌 기본 문법

useEffect(() => {
  // 실행할 코드
  return () => {
    // 정리(clean-up) 코드
  };
}, [의존성 배열]);

🧪 실습 예제

import { useEffect, useState } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    return () => {
      clearInterval(interval);
    };
  }, []);

  return <p>{seconds}초 지났습니다.</p>;
}

💡 동작 방식에 따른 분기

의존성 배열 실행 시점
[] 컴포넌트 최초 렌더링 시 한 번만
[count] count 값이 바뀔 때마다 실행
의존성 없음 모든 렌더링마다 실행

📣 주의할 점

  • 의존성 배열을 제대로 설정하지 않으면 무한 루프에 빠질 수 있어요!
  • 정리(clean-up) 함수는 컴포넌트가 사라질 때뿐만 아니라 다음 실행 전에 호출되므로 유의해야 해요.

이제 useEffect를 통해 비동기 작업이나 외부 이벤트도 자유롭게 처리할 수 있어요.

 

다음으로는, DOM을 직접 제어하거나 변경되지 않는 값을 저장할 수 있는 useRef를 알아보러 가볼까요?

 

 

3. useRef 🔍 DOM 제어와 값 기억에 강력

useRef는 리액트에서 아주 유용하지만, 처음엔 그 쓰임새가 헷갈릴 수 있는 훅이에요.

주로 DOM 요소를 직접 제어하거나 렌더링 사이에도 유지되어야 하는 값을 저장할 때 사용해요.

이 훅의 특징은 값을 변경해도 컴포넌트를 다시 렌더링하지 않는다는 거예요.

그래서 카운트 같은 상태는 useState를 쓰지만, 변경되더라도 화면에 영향을 주지 않아야 하는 값은 useRef를 쓰는 게 더 적절해요.

📌 기본 문법

const myRef = useRef(초기값);

// 접근: myRef.current

🧪 DOM 조작 예제

import { useRef } from 'react';

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

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>포커스 주기</button>
    </div>
  );
}

🧠 상태 저장 예제 (렌더링 방지)

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

function RenderCount() {
  const [value, setValue] = useState('');
  const count = useRef(1);

  useEffect(() => {
    count.current += 1;
  });

  return (
    <div>
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <p>렌더링 횟수: {count.current}</p>
    </div>
  );
}

📋 요약 정리

기능 설명
DOM 접근 ref로 지정한 요소에 직접 접근 가능
렌더링 영향 없음 값 변경 시 컴포넌트 재렌더링 발생 안 함
유지되는 값 렌더링 사이에 값을 유지하고 싶을 때 사용

useRef는 보이지 않는 메모장 같아요.

화면에 영향을 주지 않으면서 내부 데이터를 보관할 수 있거든요.

 

이 다음엔 계산이 무거운 작업을 최적화하는 useMemo로 넘어가볼게요!

 

 

4. useMemo 📦 계산 결과를 메모이제이션

리액트에서 어떤 연산이 너무 자주 반복되면, 앱이 느려지는 원인이 될 수 있어요.

useMemo는 이런 상황에서 비용이 많이 드는 계산을 기억해두고, 필요할 때만 다시 계산하게 도와줘요.

쉽게 말해서,

어떤 계산된 값이 매번 바뀌지 않는데도 계속 다시 계산되는 게 비효율적이라면,

useMemo로 감싸서 “얘는 값 안 바뀌면 계산 다시 안 해!”라고 알려주는 거예요.

📌 기본 문법

const memoizedValue = useMemo(() => {
  return 복잡한계산(입력값);
}, [의존성값]);

🧪 실습 예제

import { useState, useMemo } from 'react';

function ExpensiveComponent() {
  const [number, setNumber] = useState(1);
  const [text, setText] = useState('');

  const factorial = (n) => {
    console.log('계산 중...');
    return n <= 1 ? 1 : n * factorial(n - 1);
  };

  const memoizedFactorial = useMemo(() => factorial(number), [number]);

  return (
    <div>
      <p>{number}! = {memoizedFactorial}</p>
      <button onClick={() => setNumber(prev => prev + 1)}>증가</button>
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

💡 사용 시 주의할 점

  • 무조건 쓰면 성능이 좋아지진 않아요! 진짜 계산이 무거운 경우에만 쓰는 게 좋아요.
  • 의존성 배열을 잘못 설정하면 오래된 값을 계속 사용하게 될 수도 있어요.

📋 요약 정리

기능 설명
계산 결과 캐싱 의존값이 변하지 않으면 이전 계산 결과 재사용
최적화 대상 복잡하거나 반복되는 계산 작업
렌더링 영향 렌더링마다 계산 방지 가능

정말 계산이 무거운 로직에만 쓰는 게 핵심이에요.

불필요한 useMemo 남용은 오히려 독!

 

다음은 콜백 함수 자체를 메모이제이션해서 불필요한 재렌더링을 막는 useCallback 훅을 알아봅시다!

 

 

5. useCallback 🔄 콜백 함수를 캐싱

useCallbackuseMemo와 비슷하지만, 함수 자체를 기억하는 데 초점을 맞춘 훅이에요.

특히 하위 컴포넌트에 함수를 props로 넘길 때 불필요한 재렌더링을 막기 위해 많이 사용돼요.

리액트에서는 함수도 매번 새로 만들어지는 값이에요.

그래서 의도치 않게 하위 컴포넌트가 렌더링 될 필요가 없어도 재렌더링되곤 해요.

이때 useCallback을 사용하면 함수를 고정시켜서 그런 현상을 막을 수 있어요.

📌 기본 문법

const memoizedCallback = useCallback(() => {
  함수내용
}, [의존성값]);

🧪 실습 예제

import { useState, useCallback } from 'react';

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

  const handleClick = useCallback(() => {
    console.log('버튼 클릭!');
  }, []);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>증가</button>
      <Child onClick={handleClick} />
    </div>
  );
}

function Child({ onClick }) {
  console.log('Child 렌더링');
  return <button onClick={onClick}>클릭</button>;
}

💡 언제 써야 할까?

  • 하위 컴포넌트가 React.memo를 사용 중일 때, props로 넘기는 함수가 매번 바뀌면 재렌더링이 발생해요.
  • useCallback을 쓰면 함수 참조가 고정돼서 렌더링 최적화가 가능해져요.

📋 요약 정리

기능 설명
함수 메모이제이션 렌더링 시 동일한 함수 참조 유지
최적화 대상 React.memo와 함께 하위 컴포넌트 렌더링 방지
주의사항 의존성 배열 정확히 지정해야 효과 있음

useCallback은 성능 최적화를 위한 작은 도구예요.

얘를 잘 쓰면 쓸수록 렌더링 횟수를 줄여 앱이 쾌적해집니다.

 

다음은 컴포넌트 간 전역 상태를 쉽게 공유할 수 있는 useContext를 살펴볼 차례예요!

 

 

6. useContext 🌐 전역 상태를 간단하게 공유

리액트 앱이 커지다 보면 여러 컴포넌트에서 공통된 데이터를 사용해야 하는 상황이 많아져요.

예를 들어 사용자 정보, 테마 설정, 로그인 상태 등은 전역 상태로 관리하는 게 편하죠.

이럴 때 사용하는 게 useContext입니다.

useContext는 컴포넌트 트리 전반에 걸쳐 값을 전달하는 React Context를 쉽게 읽을 수 있도록 도와주는 훅이에요.

복잡한 props 전달 없이도 데이터를 쏙쏙! 받아올 수 있죠.

📌 기본 구조

// Context 생성
const MyContext = createContext();

// 상위 컴포넌트에서 Provider로 감싸기
<MyContext.Provider value={공유할값}>
  <하위컴포넌트 />
</MyContext.Provider>

// 하위 컴포넌트에서 사용
const value = useContext(MyContext);

🧪 실습 예제

import React, { createContext, useContext } from 'react';

const ThemeContext = createContext('light');

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button>현재 테마: {theme}</button>;
}

💡 언제 쓰면 좋을까?

  • 테마 설정, 언어 설정 등 앱 전체에 영향을 주는 값이 있을 때
  • 로그인 정보처럼 여러 컴포넌트에서 접근이 필요한 상태가 있을 때

📋 요약 정리

기능 설명
전역 상태 공유 props 없이 하위 컴포넌트에서 상태 사용 가능
Context API createContext + useContext 조합으로 구성
성능 유의사항 변경 시 모든 하위 컴포넌트 재렌더링 가능성 있음

 

이제 useContext까지 익혔다면, 리액트의 주요 훅들은 거의 섭렵한 거예요! 💪

 

다음 단계는 지금까지 배운 내용을 정리하고, 각 훅을 실제 프로젝트에서 어떻게 조합해서 사용할 수 있는지 마무리하면서 이야기해볼게요.

지금까지 리액트의 주요 훅들useState, useEffect, useRef, useMemo, useCallback, useContext—을 하나씩 차근차근 살펴봤어요.

처음에는 각 훅이 언제, 왜, 어떻게 쓰는지 헷갈릴 수 있지만, 실제 프로젝트에서 직접 활용해보면 어느 순간 손에 익는 도구처럼 자연스럽게 쓰게 됩니다.

각 훅은 역할과 목적이 명확하기 때문에 상황에 맞춰 조합하는 연습을 많이 해보는 게 좋아요.

예를 들어 useContext로 전역 상태를 만들고, 그 안에서 useMemouseCallback으로 불필요한 렌더링을 줄이는 구조는 실전에서도 많이 사용돼요.

처음 훅을 공부할 땐 “이걸 왜 쓰지?” 싶은 느낌이 들 수 있지만, 리액트를 리액트답게 쓰는 핵심이 바로 이 훅들에 있답니다.

앞으로도 계속해서 다양한 상황에 훅을 적용해보면서 자신만의 개발 노하우를 만들어 보세요! 🚀

반응형

+ Recent posts