ReactReact 기초강의 10개 중 6번째난이도 기초

useRef와 useMemo — DOM 참조와 성능 최적화

발행 읽는 시간 약 1 분

  • React
  • useRef
  • useMemo
  • useCallback
  • 성능최적화

강의 소개

useRef로 DOM 요소에 직접 접근하고, useMemo와 useCallback으로 불필요한 재계산·재렌더링을 방지하는 방법을 배웁니다.

useRef: DOM 참조#

TSX
import { useRef, useEffect } from "react";

function AutoFocusInput() {
    const inputRef = useRef<HTMLInputElement>(null);

    useEffect(() => {
        // 마운트 후 입력창에 포커스
        inputRef.current?.focus();
    }, []);

    return <input ref={inputRef} placeholder="자동 포커스" />;
}

useRef: 렌더링 없이 값 저장#

렌더링을 유발하지 않고 값을 유지합니다.

TSX
function StopWatch() {
    const [time, setTime] = useState(0);
    const [running, setRunning] = useState(false);
    const intervalRef = useRef<number | null>(null);

    function start() {
        if (running) return;
        setRunning(true);
        intervalRef.current = window.setInterval(() => {
            setTime(t => t + 1);
        }, 1000);
    }

    function stop() {
        if (intervalRef.current) clearInterval(intervalRef.current);
        setRunning(false);
    }

    // intervalRef.current 변경은 리렌더링을 유발하지 않음
    return (
        <div>
            <p>{time}초</p>
            <button onClick={start}>시작</button>
            <button onClick={stop}>정지</button>
        </div>
    );
}

useMemo: 비싼 계산 캐싱#

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

function FilteredProducts({ products }: { products: Product[] }) {
    const [query, setQuery] = useState("");
    const [minPrice, setMinPrice] = useState(0);

    // query나 minPrice, products가 바뀔 때만 재계산
    const filtered = useMemo(() => {
        console.log("필터링 실행");  // 렌더링마다 실행되지 않음
        return products
            .filter(p => p.name.includes(query))
            .filter(p => p.price >= minPrice);
    }, [products, query, minPrice]);

    return (
        <div>
            <input value={query} onChange={e => setQuery(e.target.value)} />
            <input type="number" value={minPrice} onChange={e => setMinPrice(Number(e.target.value))} />
            <ul>
                {filtered.map(p => <li key={p.id}>{p.name}</li>)}
            </ul>
        </div>
    );
}

useCallback: 함수 참조 안정화#

TSX
import { useCallback, memo } from "react";

// memo: props가 같으면 리렌더링 건너뜀
const ExpensiveChild = memo(({ onAction }: { onAction: () => void }) => {
    console.log("자식 렌더링");
    return <button onClick={onAction}>액션</button>;
});

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

    // useCallback 없이: 매 렌더링마다 새 함수 → 자식도 리렌더링
    // useCallback 사용: 함수 참조 유지 → 자식 리렌더링 없음
    const handleAction = useCallback(() => {
        console.log("액션 실행");
    }, []);  // 의존성 없으면 최초 1회만 생성

    return (
        <div>
            <input value={name} onChange={e => setName(e.target.value)} />
            <p>{count}</p>
            <ExpensiveChild onAction={handleAction} />
        </div>
    );
}

언제 최적화가 필요한가?#

Diagram · Mermaid

성능 최적화 원칙:

  1. 먼저 동작하게 만들고
  2. 측정 후 필요할 때만 최적화

useId: 고유 ID 생성#

TSX
import { useId } from "react";

function FormField({ label }: { label: string }) {
    const id = useId();  // 서버-클라이언트 일관된 고유 ID

    return (
        <div>
            <label htmlFor={id}>{label}</label>
            <input id={id} type="text" />
        </div>
    );
}

정리#

Hook용도
useRefDOM 참조, 렌더링 없는 값 저장
useMemo비싼 계산 결과 캐싱
useCallback함수 참조 안정화
memo()props 변화 없으면 리렌더링 건너뜀
useId고유 ID 생성

다음 편에서는 Context API — prop drilling 없이 전역 상태를 공유하는 방법을 배웁니다.

Follow · Ask · Work

이 글이 도움이 됐다면

짧은 작업 기록과 데모는 인스타그램에 올립니다. 만들고 싶은 것이 있다면 프로젝트 의뢰나 협업 제안도 편하게 보내 주세요.

글쓴이

파란여우

직접 만든 AI 앱·제품의 개발·배포를 공개하는 실전형 테크 크리에이터

파란여우 소개 Instagram @bluefox._.hi

이어서 보기

만들고 싶은 것이 있다면 프로젝트 의뢰로 알려 주세요.