누구나 로고
Online CoursesFor BusinessLibraryTrend InsightsFree coursesNoticesContactCareers
모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.
로그인무료 회원가입
강좌리액트 입문 — 컴포넌트로 만드는 웹14강

14강 / 전체 28강

컴포넌트 합성과 재사용

5분 읽기 조회 0

useRef로 DOM 요소에 직접 접근하고, 렌더링 없이 값을 유지하는 방법, forwardRef 패턴까지 useRef의 두 가지 역할을 완전히 이해합니다.

🔍 useRef — 두 가지 역할

useRef는 용도가 두 가지입니다. 혼동하기 쉬우니 처음부터 명확히 구분합니다:

  1. DOM 요소에 직접 접근: 특정 DOM 요소를 직접 제어할 때 (포커스, 스크롤, 크기 측정 등)
  2. 렌더링 없이 값 유지: 리렌더링을 일으키지 않고 값을 기억해야 할 때 (이전 값 저장, 타이머 ID 등)

📍 역할 1 — DOM 요소에 접근하기

입력창 자동 포커스, 스크롤 위치 제어 같은 작업은 DOM에 직접 접근해야 합니다. useRef로 요소에 연결합니다:

import { useRef, useEffect } from 'react'

function SearchModal({ isOpen }: { isOpen: boolean }) {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    // 모달이 열릴 때 자동으로 입력창에 포커스
    if (isOpen && inputRef.current) {
      inputRef.current.focus()
    }
  }, [isOpen])

  return (
    <div>
      {/* ref 속성으로 DOM 요소와 연결 */}
      <input
        ref={inputRef}
        type="search"
        placeholder="검색..."
        className="border rounded px-4 py-2 w-full"
      />
    </div>
  )
}

inputRef.current가 실제 DOM 요소입니다. useEffect 없이 렌더링 중에 inputRef.current를 읽으면 null입니다 — DOM은 렌더링 후에 연결됩니다.

💾 역할 2 — 렌더링 없이 값 유지

useState는 값이 바뀌면 리렌더링을 일으킵니다. 그런데 때로는 값은 기억하되, 리렌더링은 일으키고 싶지 않을 때가 있습니다. 타이머 ID가 대표적입니다:

function Stopwatch() {
  const [time, setTime] = useState(0)
  const [isRunning, setIsRunning] = useState(false)
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)

  function start() {
    if (isRunning) return
    setIsRunning(true)
    intervalRef.current = setInterval(() => {
      setTime((prev) => prev + 1)
    }, 1000)
  }

  function stop() {
    if (intervalRef.current !== null) {
      clearInterval(intervalRef.current)
      intervalRef.current = null
    }
    setIsRunning(false)
  }

  function reset() {
    stop()
    setTime(0)
  }

  return (
    <div className="text-center space-y-4">
      <p className="text-4xl font-mono font-bold">{time}초</p>
      <div className="flex gap-2 justify-center">
        {!isRunning ? (
          <button onClick={start} className="px-4 py-2 bg-green-500 text-white rounded">시작</button>
        ) : (
          <button onClick={stop} className="px-4 py-2 bg-red-500 text-white rounded">정지</button>
        )}
        <button onClick={reset} className="px-4 py-2 bg-gray-200 rounded">초기화</button>
      </div>
    </div>
  )
}

intervalRef를 useState로 관리하면 타이머 ID가 바뀔 때마다 불필요한 리렌더링이 발생합니다. useRef로 관리하면 값은 유지되지만 리렌더링 없이 조용히 저장됩니다.

⏪ 이전 값 기억하기

useRef로 이전 렌더의 값을 기억하는 유용한 패턴입니다:

function PriceDisplay({ price }: { price: number }) {
  const prevPriceRef = useRef<number>(price)

  useEffect(() => {
    prevPriceRef.current = price  // 렌더 후 이전 값 업데이트
  })

  const prevPrice = prevPriceRef.current
  const change = price - prevPrice

  return (
    <div>
      <span className="text-2xl font-bold">{price.toLocaleString()}원</span>
      {change !== 0 && (
        <span className={change > 0 ? 'text-red-500' : 'text-blue-500'}>
          {change > 0 ? '▲' : '▼'} {Math.abs(change).toLocaleString()}
        </span>
      )}
    </div>
  )
}

🔗 forwardRef — ref를 자식 컴포넌트로 전달

직접 만든 컴포넌트에는 ref를 기본으로 전달할 수 없습니다. forwardRef로 감싸야 합니다:

import { forwardRef } from 'react'

interface InputProps {
  label: string
  placeholder?: string
}

// forwardRef로 ref를 받아서 실제 input 요소로 전달
const CustomInput = forwardRef<HTMLInputElement, InputProps>(({ label, placeholder }, ref) => {
  return (
    <label className="block">
      <span className="text-sm font-medium text-gray-700">{label}</span>
      <input
        ref={ref}  // 전달받은 ref를 DOM에 연결
        placeholder={placeholder}
        className="mt-1 block w-full border rounded px-3 py-2"
      />
    </label>
  )
})

CustomInput.displayName = 'CustomInput'  // DevTools 표시명

// 사용
function ParentForm() {
  const nameInputRef = useRef<HTMLInputElement>(null)

  function focusName() {
    nameInputRef.current?.focus()
  }

  return (
    <div>
      <CustomInput ref={nameInputRef} label="이름" />
      <button onClick={focusName}>이름 입력창 포커스</button>
    </div>
  )
}

✅ 정리

  • useRef는 ① DOM 요소 접근, ② 렌더링 없이 값 유지 두 가지 역할입니다.
  • ref.current로 값에 접근합니다. DOM ref는 렌더링 후에야 연결됩니다.
  • 타이머 ID, 이전 값 등 리렌더링 없이 유지할 값에 사용합니다.
  • 직접 만든 컴포넌트에 ref를 전달하려면 forwardRef로 감쌉니다.

관련 주제

  • composition 패턴
  • children 슬롯
  • 재사용 가능한 컴포넌트 설계
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 리액트 입문 — 컴포넌트로 만드는 웹
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나
이전 13강상태 끌어올리기다음 15강 useRef와 DOM 접근

댓글

0/1000

불러오는 중...

목차

  1. 1.리액트란 무엇인가
  2. 2.개발 환경 만들기 — Vite
  3. 3.JSX 기초
  4. 4.컴포넌트 만들기
  5. 5.Props로 데이터 전달
  6. 6.State와 useState
  7. 7.이벤트 처리
  8. 8.조건부 렌더링
  9. 9.리스트 렌더링과 key
  10. 10.폼 다루기
  11. 11.useEffect 기초
  12. 12.데이터 가져오기(fetch)
  13. 13.상태 끌어올리기
  14. 14.컴포넌트 합성과 재사용
  15. 15.useRef와 DOM 접근
  16. 16.커스텀 훅 만들기
  17. 17.Context API
  18. 18.useReducer
  19. 19.리스트 앱 실전 ①
  20. 20.리스트 앱 실전 ②
  21. 21.라우팅 — React Router
  22. 22.스타일링 방법들
  23. 23.리스트·상세 페이지 연결
  24. 24.비동기 상태 관리 패턴
  25. 25.성능 기초 — memo
  26. 26.에러 처리와 경계
  27. 27.환경변수와 빌드
  28. 28.배포하기

Support

  • Notices
  • FAQ
  • Contact Us
  • Community

Help & Info

  • Terms of Service
  • Privacy Policy
  • Refund Policy

Services

  • About Us
  • Sign up
  • New courses
  • Free courses
누구나 로고
Terms of ServicePrivacy PolicyRefund Policy

상호명: NUGUNA  |  대표자: 정우진  |  사업자등록번호: 392-32-01817  |  통신판매업신고: 제 2026-서울양천-0564 호

주소: 서울특별시 양천구 목동서로 100  |  이메일: nugunapass@gmail.com  |  전화: 010-6395-3043

© 2026 NUGUNA. All rights reserved.

KB예금주인증관리자
HomeCoursesLibraryContactMY