모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

15강 / 전체 30강

useRef와 DOM 접근

6분 읽기 조회 5

반복되는 상태 로직을 커스텀 훅으로 추출하는 방법, 좋은 커스텀 훅의 설계 원칙, useToggle·useLocalStorage·useDebounce 세 가지 실전 훅을 직접 만듭니다.

🔩 커스텀 훅이란

여러 컴포넌트에서 동일한 상태 로직이 반복될 때, 그것을 커스텀 훅(Custom Hook)으로 추출합니다. 커스텀 훅은 이름이 use로 시작하는 일반 함수입니다. 다른 훅들(useState, useEffect 등)을 내부에서 사용할 수 있고, 여러 컴포넌트에서 로직을 재사용할 수 있습니다.

커스텀 훅이 컴포넌트와 다른 점: 훅은 UI를 반환하지 않습니다. 상태와 함수(그리고 필요하면 값)를 반환합니다.

1️⃣ useToggle — 불리언 토글

가장 단순하지만 실무에서 자주 쓰이는 패턴입니다. 모달 열기/닫기, 드롭다운, 체크박스 등에 활용합니다:

// hooks/useToggle.ts
import { useState, useCallback } from 'react'

export function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue)

  const toggle = useCallback(() => setValue((prev) => !prev), [])
  const setTrue = useCallback(() => setValue(true), [])
  const setFalse = useCallback(() => setValue(false), [])

  return { value, toggle, setTrue, setFalse }
}
// 사용
function Modal() {
  const { value: isOpen, toggle, setFalse: close } = useToggle()

  return (
    <>
      <button onClick={toggle}>모달 열기</button>
      {isOpen && (
        <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
          <div className="bg-white rounded-2xl p-6">
            <p>모달 내용</p>
            <button onClick={close}>닫기</button>
          </div>
        </div>
      )}
    </>
  )
}

2️⃣ useLocalStorage — 새로고침 후에도 유지되는 상태

테마, 언어 설정, 마지막 검색어 등 브라우저를 닫아도 유지돼야 하는 상태에 사용합니다:

// hooks/useLocalStorage.ts
import { useState, useEffect } from 'react'

export function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    // SSR 환경(Next.js 등) 대응
    if (typeof window === 'undefined') return initialValue
    try {
      const item = window.localStorage.getItem(key)
      return item ? (JSON.parse(item) as T) : initialValue
    } catch {
      return initialValue
    }
  })

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(value))
    } catch {
      // 시크릿 모드 등에서 localStorage 접근 불가 시 무시
    }
  }, [key, value])

  return [value, setValue] as const
}
// 사용 — useState와 완전히 동일한 인터페이스!
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light')

  return (
    <button
      onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))}
      className="px-4 py-2 border rounded"
    >
      현재 테마: {theme} (새로고침해도 유지됩니다)
    </button>
  )
}

3️⃣ useDebounce — 입력 지연 처리

검색창에서 타이핑할 때마다 API를 호출하면 너무 많은 요청이 발생합니다. 입력이 멈춘 뒤 일정 시간 후에만 실행되도록 디바운스(debounce)를 적용합니다:

// hooks/useDebounce.ts
import { useState, useEffect } from 'react'

export function useDebounce<T>(value: T, delay = 300): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value)

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)

    return () => clearTimeout(timer)  // 새 값이 오면 이전 타이머 취소
  }, [value, delay])

  return debouncedValue
}
// 사용
function SearchInput() {
  const [query, setQuery] = useState('')
  const debouncedQuery = useDebounce(query, 500)  // 0.5초 후 확정

  const { data: results } = useFetch<Result[]>(
    debouncedQuery ? `/api/search?q=${debouncedQuery}` : ''
  )

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="검색..." 
        className="border rounded px-3 py-2"
      />
      {/* results가 있으면 표시 */}
    </div>
  )
}

타이핑 중에는 query가 계속 바뀌지만 debouncedQuery는 500ms 동안 입력이 없을 때만 업데이트됩니다. API 요청이 크게 줄어듭니다.

📐 좋은 커스텀 훅 설계 원칙

  • 한 가지 책임: 한 훅은 한 가지 관심사만 담습니다. 너무 많은 것을 넣으면 재사용성이 떨어집니다.
  • useState처럼 반환: 반환값 인터페이스를 useState처럼 직관적으로 만드세요.
  • 초기값 파라미터: 기본값을 제공해 유연성을 높입니다.
  • TypeScript 제네릭: useLocalStorage<T>처럼 제네릭으로 타입 안전성을 보장합니다.

✅ 정리

  • 커스텀 훅: use로 시작하는 함수, 상태 로직을 재사용 가능하게 추출합니다.
  • useToggle: 불리언 상태와 toggle/setTrue/setFalse 반환.
  • useLocalStorage: localStorage와 동기화된 상태, SSR 안전 처리 포함.
  • useDebounce: 값 변경을 지연시켜 API 요청 빈도를 줄입니다.

관련 주제

  • useRef 개념
  • DOM 요소 직접 접근
  • 포커스 제어
  • 렌더링과 무관한 값 저장
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

리액트 입문 — 컴포넌트로 만드는 웹

이 강의가 속한 시리즈는 총 30강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...