본문 바로가기
JavaScript/React

useRef, useImperativeHandle

by curious week 2025. 3. 21.

useRef

1. useRef란?

  • React의 상태(useState)와는 다르게 리렌더링 없이 값을 저장할 수 있는 Hook.
  • DOM 요소에 직접 접근하거나, 이전 값을 저장할 때 사용됨.
  • 값이 변경되어도 컴포넌트가 리렌더링되지 않음.

2. useRef 기본 문법

import { useRef } from "react";

const Example = () => {
  const inputRef = useRef(null); // 초기값 설정

  const focusInput = () => {
    inputRef.current.focus(); // input에 포커스 적용
  };

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="클릭하면 포커스" />
      <button onClick={focusInput}>포커스</button>
    </div>
  );
};

export default Example;

useRef를 사용하면 input 요소에 직접 접근하여 포커스를 적용할 수 있음.
리렌더링이 발생하지 않음 → useState를 사용했다면 버튼을 누를 때마다 렌더링이 발생함.


3. useRef의 주요 활용 사례

1) DOM 요소 직접 접근 (input, button 등)

import { useRef } from "react";

const Example = () => {
  const buttonRef = useRef(null);

  return (
    <button ref={buttonRef} onClick={() => buttonRef.current.style.background = "red"}>
      버튼 색 변경
    </button>
  );
};

export default Example;

buttonRef.current.style.background = "red"를 통해 버튼 색상 변경 가능.
useRef는 상태를 변경하지 않으므로 리렌더링 없이 DOM 조작 가능.


2) 이전 값 저장 (prevValue 관리)

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

const Example = () => {
  const [count, setCount] = useState(0);
  const prevCount = useRef(count);

  useEffect(() => {
    prevCount.current = count; // 이전 count 값을 저장
  }, [count]);

  return (
    <div>
      <h1>현재 값: {count}</h1>
      <h2>이전 값: {prevCount.current}</h2>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
};

export default Example;

useRef를 사용하여 이전 값을 저장할 수 있음.
useEffect를 활용하여 prevCount.current를 업데이트.
이전 값(prevCount.current)은 컴포넌트가 리렌더링되더라도 유지됨.


3) setTimeout, setInterval을 useRef로 관리

import { useRef, useState } from "react";

const Timer = () => {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);

  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
  };

  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={startTimer}>시작</button>
      <button onClick={stopTimer}>정지</button>
    </div>
  );
};

export default Timer;

intervalRef.current에 setInterval 값을 저장하여 언제든지 중지 가능.
리렌더링이 발생해도 setInterval ID가 유지됨.


4) useRef vs useState

렌더링을 방지하려면 useRef 사용
UI 업데이트가 필요하면 useState 사용


4. useRef 사용 시 주의할 점

1) useRef 값을 변경해도 리렌더링되지 않음

const Example = () => {
  const countRef = useRef(0);

  return (
    <div>
      <h1>{countRef.current}</h1> {/* 화면에 보이는 값은 업데이트되지 않음 */}
      <button onClick={() => countRef.current += 1}>증가</button>
    </div>
  );
};

countRef.current 값이 변경되어도 화면이 업데이트되지 않음.
화면 업데이트가 필요하면 useState를 사용해야 함.


2) ref는 null로 초기화된 상태일 수 있음

const Example = () => {
  const inputRef = useRef(null);

  const handleClick = () => {
    console.log(inputRef.current); // 초기에는 null일 수도 있음
  };

  return <button onClick={handleClick}>콘솔 확인</button>;
};

초기에는 inputRef.current가 null일 수도 있으므로, 항상 체크해야 함.


useImperativeHandle

1. useImperativeHandle란?

  • React에서 ref를 사용할 때, 부모 컴포넌트가 자식 컴포넌트의 특정 메서드나 속성에만 접근할 수 있도록 제한하는 Hook.
  • forwardRef와 함께 사용해야 함.
  • DOM 요소뿐만 아니라, 컴포넌트의 특정 기능을 부모가 직접 호출할 수 있도록 함.

2. useImperativeHandle 기본 문법

import { useImperativeHandle, useRef, forwardRef } from "react";

// 자식 컴포넌트
const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    showAlert: () => {
      alert("자식 컴포넌트에서 실행됨!");
    },
  }));

  return <h1>자식 컴포넌트</h1>;
});

// 부모 컴포넌트
const Parent = () => {
  const childRef = useRef();

  return (
    <>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.showAlert()}>실행</button>
    </>
  );
};

export default Parent;

useImperativeHandle을 사용하면, 부모가 ref를 통해 자식의 특정 함수만 호출할 수 있음.
forwardRef를 사용해야 ref를 자식 컴포넌트로 전달할 수 있음.
부모는 childRef.current.showAlert()을 호출하여 자식의 메서드를 실행 가능.


3. useImperativeHandle이 필요한 이유

1) 부모가 자식의 특정 기능만 실행하도록 제한

  • 일반적으로 ref는 DOM 요소에 직접 접근할 때 사용됨.
  • 하지만 useImperativeHandle을 사용하면 자식 컴포넌트의 특정 메서드만 부모가 호출할 수 있도록 제한할 수 있음.
const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    showAlert: () => {
      alert("자식 내부에서 실행됨");
    },
  }));

  // 부모가 직접 접근하지 못하도록 내부 상태를 관리
  const privateFunction = () => {
    console.log("부모는 이 함수에 접근할 수 없음");
  };

  return <h1>자식 컴포넌트</h1>;
});

부모는 showAlert()만 실행할 수 있고, privateFunction()에는 접근할 수 없음.


2) ref를 이용한 포커스 제어

  • 부모가 자식 컴포넌트의 input에 포커스를 강제 적용하고 싶을 때.
import { useImperativeHandle, forwardRef, useRef } from "react";

const InputField = forwardRef((props, ref) => {
  const inputRef = useRef();

  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
  }));

  return <input ref={inputRef} type="text" />;
});

const Parent = () => {
  const inputRef = useRef();

  return (
    <div>
      <InputField ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>포커스</button>
    </div>
  );
};

export default Parent;

부모의 ref를 통해 focus()를 호출하면, 자식의 input 요소에 포커스를 적용할 수 있음.


3) Modal을 외부에서 열고 닫기

  • useImperativeHandle을 사용하면 부모에서 ref를 통해 모달을 제어할 수 있음.
import { useImperativeHandle, forwardRef, useState, useRef } from "react";

const Modal = forwardRef((props, ref) => {
  const [visible, setVisible] = useState(false);

  useImperativeHandle(ref, () => ({
    open: () => setVisible(true),
    close: () => setVisible(false),
  }));

  if (!visible) return null;

  return (
    <div className="modal">
      <h2>모달 창</h2>
      <button onClick={() => setVisible(false)}>닫기</button>
    </div>
  );
});

const App = () => {
  const modalRef = useRef();

  return (
    <div>
      <button onClick={() => modalRef.current.open()}>모달 열기</button>
      <Modal ref={modalRef} />
    </div>
  );
};

export default App;

부모에서 modalRef.current.open()을 호출하면 모달이 열리고, modalRef.current.close()로 닫을 수 있음.
useState로 상태를 관리하면서, 부모가 특정 메서드를 통해 상태를 변경할 수 있도록 제어.


4. useImperativeHandle과 useRef 차이점

 

useRef DOM 요소 직접 접근 부모가 접근 가능한 범위: 모든 DOM 속성 (input.value, div.style 등)
useImperativeHandle 부모가 자식의 특정 메서드만 실행 부모가 접근 가능한 범위: useImperativeHandle에서 정의한 메서드만 접근 가능

즉, useImperativeHandle을 사용하면 부모가 접근할 수 있는 기능을 제한할 수 있음!
보안성 및 코드의 명확성을 높이는 데 유용.


5. useImperativeHandle은 언제 사용해야 할까?

부모가 자식의 특정 기능만 실행할 수 있도록 제한하고 싶을 때
부모가 자식의 ref를 통해 특정 동작(포커스, 모달 열기 등)을 수행해야 할 때
보안성을 강화하여 부모가 불필요한 내부 상태에 접근하지 못하도록 하고 싶을 때

🚨 주의! useImperativeHandle을 남용하면 부모가 자식의 내부 동작을 과도하게 제어하게 될 수 있음.
🚨 컴포넌트 간 의존성이 강해지므로 꼭 필요한 경우에만 사용해야 함!

 

'JavaScript > React' 카테고리의 다른 글

Jest와 React Test Library(RTL)  (0) 2025.03.25
React 고급 Hook  (0) 2025.03.21
useEffect, useLayoutEffect, useInsertionEffect  (0) 2025.03.21
React Router DOM  (0) 2025.03.21
React 기본 개념  (0) 2025.03.21