• hooks 2025.10.15 #개발/react

useState: 값 저장 및 변경
useEffect: 부수효과 (API 등)
useContext: 전역 데이터 읽기
useRef: DOM 참조/기억
useMemo/useCallback: 성능 최적화
useReducer: 복잡한 상태변화

 


Hook 이름 사용 방법 주 사용 목적 동작원리 주요 사용 케이스
useState const [state, setState] = useState(초깃값); 간단 상태값/ 로직 상태값을 컴포넌트에 바인딩(초기값 등록). setter 호출 시 React가 상태 저장 및 리렌더링 숫자 카운터, 입력값 관리, 토글 등 간단한 상태 저장
useEffect useEffect(() ⇒ { effect }, [의존성]) 사이드이펙트(부수효과) 관리 렌더 후 사이드이펙트(부수효과) 실행. 의존성이 바뀌거나 mount/unmount 때 실행 API 콜, 타이머, 이벤트 구독/해제 
useContext const value = useContext(MyContext); 글로벌 상태, 전역 데이터 공급 Context.Provider에서 제공한 데이터를 쉽게 읽음 전역 상태, 언어 전역 설정, 테마 관리 등
최상위 부터 하위까지 prop drilling 없이 자식 요소까지 값 공유
useRef const ref = useRef(초깃값); 리렌더 필요 없이 "값/ DOM" 저장 변수를 리렌더링 없이 “참조(ref) 공간”에 저장. .current로 접근 DOM 요소 직접 조작,
이전 값 저장,
setTimeout 등 외부 값 저장
useMemo const value = useMemo(() ⇒ expensiveFn(), [의존성]); '비싼 계산 결과' 캐싱 특정 연산 결과를 의존성 배열 기준으로 “메모이제이션”(캐싱) 연산이 무거운 값,
불필요한 재계산 방지,
렌더 성능 최적화 필요할 때
useCallback const fn = useCallback(() ⇒ { ... }, [의존성]); '함수' 캐싱, 자식 렌더링 최적화 함수(콜백)를 “메모이제이션”(생성/바뀌는 타이밍 최소화) 자식에게 함수를 prop으로 넘길 때 불필요한 재생성 방지,
useMemo/ useEffect의 의존성 배열에 함수가 들어갈 때 렌더 최적화
(함수 생성 자체의 리소스 절약이 주목적이 아님)
useReducer const [state, dispatch] = useReducer(reducer, 초기값); 복잡 로직, 다양한 액션/ 상태 다양한 action에 따른 상태 업데이트 로직을 한 곳에(리듀서로) 집중 복잡한 상태 관리, 여러 액션/상태가 얽힌 폼 등에서 사용

1. useState

심화 설명

  • 컴포넌트 개별 상태값을 함수형 컴포넌트에서도 사용할 수 있게 하는 Hook
  • setter 함수 호출 시 React는 해당 컴포넌트만 리렌더
  • 상태값과 Setter는 컴포넌트마다 독립적으로 유지

코드 예제

import React, { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0); // count: 상태값, setCount: 상태 변경함수

  return (
    <div>
      <button onClick={() => setCount(count - 1)}>-</button>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

설명:
클릭할 때마다 상태(count) 값이 변하고, 변할 때마다 화면이 다시 그려집니다.

 

2. useEffect

심화 설명

  • **렌더 후 수행되는 "부수 효과(side-effect)"**를 관리
  • 컴포넌트가 마운트/업데이트/언마운트 되는 시점을 제어 가능
  • 의존성 배열 값을 기준으로 언제 effect를 실행할지 설정

코드 예제

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

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

  useEffect(() => {
    const timer = setInterval(() => setSeconds(s => s + 1), 1000);
    // 언마운트 시 타이머 제거
    return () => clearInterval(timer);
  }, []); // []: 최초 1회만 실행 (마운트/언마운트)

  return <div>{seconds}초 지남</div>;
}

설명:
setInterval로 초를 증가시키되, 컴포넌트가 사라질 때(return) 타이머 삭제

 

3. useContext

심화 설명

  • 컴포넌트 트리 전체에 데이터(전역 상태 등)를 prop drilling 없이 공급
  • 값을 읽어올 때마다 최신 컨텍스트 값을 자동 반영
  • 주로, 테마, 로그인 정보, 언어 설정 등에 사용

코드 예제

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

const ThemeContext = createContext("light");

function ThemedButton() {
  const theme = useContext(ThemeContext); // 컨텍스트 값 읽기
  return <button style={{ background: theme === "dark" ? "#222" : "#eee" }}>Button</button>
}

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

설명:
ThemedButton은 ThemeContext의 값을 받아 버튼의 스타일을 자동으로 변경합니다.

 

 

5. useMemo / useCallback

심화 설명 (둘의 차이)

  • useMemo: 비싼 연산(무거운 계산 결과)를 캐시
  • useCallback: 함수를 캐시 (같은 함수가 자식에 계속 전달될 때 불필요한 렌더 방지)
    • props로 함수를 전달할 때 (특히 React.memo/자식컴포넌트에)
      • 함수 주소가 바뀌지 않게 하려면 useCallback 필요
    • 의존성 배열에 함수가 들어가는 useEffect/useMemo 사용할 때
    • 많은 수의 컴포넌트 리렌더링/최적화가 중요한 경우
    • 불필요하게 모든 함수에 useCallback을 남용하면, 메모리와 관리 비용이 오히려 늘 수 있음
    • 부모의 state/props를 사용하는 함수일 경우, 그 값이 변하면 useCallback도 새 함수가 됨
      (의존성 배열에 값을 꼭 명확히 넣어야 함)

useMemo 코드 예제

const expensiveValue = useMemo(() => {
  return heavyComputation(num); // num 바뀔 때만 재계산
}, [num]);

설명:
num 값이 바뀔 때만 heavyComputation() 실행, 그 외 캐시 사용.

1. 대규모 리스트 필터링 & 소트 (비효율적 렌더링 방지)

대규모 데이터 배열에서 필터링과 정렬 같은 연산이 자주 일어날 때, 상태(state)가 변할 때마다 무조건 연산하면 성능에 치명적입니다.
이럴 때 useMemo로 의존성 값이 바뀔 때만 계산하도록 만들어 렌더 성능을 최적화합니다.

예시 코드

import React, { useState, useMemo } from "react";

// 수천 개의 유저 리스트가 있다고 가정
const userList = [...Array(10000)].map((_,i) => ({
  id: i, name: `User ${i}`, age: Math.floor(Math.random() * 80)
}));

function UserListApp() {
  const [keyword, setKeyword] = useState("");
  const [sortByAge, setSortByAge] = useState(false);

  // userList 필터링 & 정렬 연산을 useMemo로 캐싱
  const filteredSortedUsers = useMemo(() => {
    let list = userList.filter(user => user.name.toLowerCase().includes(keyword.toLowerCase()));
    if (sortByAge) list = list.sort((a,b) => a.age - b.age);
    console.log('expensive filtration!'); // 실제 렌더링 횟수 확인용
    return list;
  }, [keyword, sortByAge]); // keyword나 sortByAge가 바뀔 때만 재계산

  return (
    <div>
      <input placeholder="Search user" value={keyword}
        onChange={e => setKeyword(e.target.value)} />
      <button onClick={() => setSortByAge(s => !s)}>
        {sortByAge ? "나이순 정렬 해제" : "나이순 정렬"}
      </button>
      <ul>
        {filteredSortedUsers.map(user =>
          <li key={user.id}>{user.name} ({user.age})</li>
        )}
      </ul>
    </div>
  );
}

설명:

  • 컴포넌트 리렌더: 모든 state 변경 시 항상 발생
  • useMemo 계산: 오직 의존성 배열 항목(keyword, sortByAge)이 바뀔 때만 발생
  • 나머지 경우: 아무리 컴포넌트가 리렌더돼도 useMemo 결과(비싼 연산)는 재사용됨

이렇게 불필요한 연산 제외로 대규모 데이터 처리 시 성능을 크게 향상시킬 수 있습니다.

 

 

useCallback 코드 예제

const handleClick = useCallback(() => {
  setCount(c => c + 1);
}, []); // 의존값이 없으면 최초 1회만 생성

// 자식에 handleClick 등 함수를 prop으로 줄 때, 불필요한 재생성 방지
  • useEffect/useMemo의 의존성 배열 안에 함수가 들어가고, 그 함수가 리렌더마다 새로 만들어질 수 있다면 → useCallback으로 감싸주는 게 필수!
  • 그렇지 않으면, 본래 의도와 다르게 useEffect/useMemo가 과도하게 실행됨
function Parent({ value }) {
  // 매 렌더마다 handleChange는 새로운 함수임
  const handleChange = (v) => {
    console.log("changed:", v);
  };

  useEffect(() => {
    // handleChange가 새 함수로 인식돼서, value와 상관없이 매번 실행됨
    handleChange(value);
  }, [value, handleChange]); 
  // 여기서 handleChange는 매번 새 함수라 useEffect가 매번 작동

  return <div>{value}</div>;
}
  • 문제점:
    • value가 변하지 않아도, Parent가 리렌더될 때마다 handleChange가 바뀌므로
    • useEffect가 쓸데없이 반복 실행
function Parent({ value }) {
  // useCallback으로 함수 재사용 (value가 변할 때만 새로 만듦)
  const handleChange = useCallback((v) => {
    console.log("changed:", v);
  }, []);

  useEffect(() => {
    handleChange(value);
  }, [value, handleChange]);
  // 이제 handleChange가 변하지 않으므로, useEffect는 value 변화시에만 실행

  return <div>{value}</div>;
}
  • 해결:
    • handleChange가 의미 있게 바뀔 때만 새로 만들어짐
    • useEffect가 불필요하게 반복 실행되지 않음

 

 

 

6. useReducer

심화 설명

  • 복잡 상태(여러 값, 다양한 action, 상태 전이 필요 시) 사용
  • reducer 함수 형태로 state, action에 따라 새 상태를 산출
  • Redux 패턴의 간소화 버전

코드 예제

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <span>{state.count}</span>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
}

설명:
상태(state), action(무엇을 할지)에 따라 상태 관리. 복잡한 폼, 여러 입력값, 다양한 이벤트 트리거에 적

+ Recent posts