Life is connecting the dots .

useImperativeHandle은 언제 사용할까? 본문

Programming/React

useImperativeHandle은 언제 사용할까?

soyeori 2025. 2. 27. 11:14

최근 프로젝트를 하면서 useImperativeHandle hook에 대해 알게 되었다. 이번 포스팅은 해당 hook이 무엇인지, 언제 사용하는지, 어떻게 사용하는지에 대해 정리해 두고자 작성한다.

 

정의

useImperativeHandle은 React Hook으로 ref로 커스텀 메소드를 만들 수 있게 해 준다. 다시 말하면, 사용자 정의 ref 핸들러를 부모 컴포넌트에서 사용할 수 있도록 노출시킬 수 있다. 사용법은 컴포넌트 최상위 레벨에서 useImperativeHandle을 호출해서 노출할 ref에 커스텀 메소드(createHandle)를 작성해 준다. 

// (참고) React v19

// (1)
import { useImperativeHandle } from 'react';

// 자식 컴포넌트
function MyInput({ ref }) {
  // useImperativeHandle(ref, createHandle, dependencies?)
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
    };
  }, []);

  return <input />;
  
}

 

ref 전달 방법 (React v19)

최근 React 19 버전을 사용하면서 ref 전달 방식이 변경된 것을 알게 되었다. 기존에는 ref를 전달하기 위해 forwardRef를 사용해야 했다. 하지만 19 버전에서는 함수형 컴포넌트에서 더 이상 forwardRef는 사용하지 않고, prop으로 ref를 전달하고 접근할 수 있다. 

 

사용법

그럼 useImperativeHandle을 어떤 상황에서 사용할 수 있을까? 

 

특정 DOM node만 노출시키고 싶을 때

  • React에서 node에 대해 focus, scroll, size, position 측정 등의 작업이 필요할 때 DOM node를 참조해야 하는데 이를 위해 DOM node에 접근하기 위해 useRef hook을 사용한다. 이처럼 useRef는 DOM node 전체를 접근할 수 있다.
  • 반면에  useImperativeHandle을 사용하면 불필요한 DOM 접근을 제한하면서 필요한 기능만 안전하게 외부에 전달할 수 있다. 

 

예를 들어, 로그인 입력폼을 만들고 있다고 가정해 보자.

 

사용자가 로그인 버튼을 눌렀을 때 특정 입력창에 자동으로 포커스 되거나, 에러가 발생한 입력 필드에 포커스를 이동하게끔 만들고 싶다면 focus() 기능만을 사용할 것이다. 이때 useImperativeHandle을 사용하면 DOM node 전체를 접근하지 않고 필요한 기능만을 사용할 수 있다.

import { useRef, useImperativeHandle } from 'react';

// (2)
function MyInput({ ref }) {
  const inputRef = useRef(null); // <input> DOM node를 참조

  // useImperativeHandle를 사용하여 부모 컴포넌트에 접근할 수 있는 메소드를 제한
  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return <input ref={inputRef} />;
};

 

위 코드를 보면 MyInput 컴포넌트에서 ref를 생성하여 부모에게 전달받은 ref가 실행될 때 실행할 메소드를 정의해 주었다. 처음 (1) 번 코드와 비교하면, (1) 번은 input에 ref를 전달하지 않고 모든 <input> DOM node를 접근할 수 있는 반면, (2) 번은 ref를 전달하여 원하는 메소드만 실행할 수 있도록 정의할 수 있다. 

 

즉, 동작 방식을 정리해 보면

  • useRef(inputRef)를 사용해서 <input> DOM node를 참조
  • useImperativeHandle를 사용하여 부모 컴포넌트에 접근할 수 있는 메소드를 제한
    • focus()와 scrollIntoView()만 제공
    • 즉, ref.current는 <input> DOM node가 아닌 특정 메소드만 가진 객체가 된다.
    • 그렇기 때문에 부모 컴포넌트에서 ref.current.style.opacity = 0.5; 와 같이 지정한 메소드 외 DOM node에 접근하려고 하면 실행되지 않는다.
// 부모 컴포넌트
import { useRef } from 'react';
import MyInput from './MyInput.js';

export default function Form() {
  const ref = useRef(null);

  // ref.current는 input DOM node가 아닌 특정 메소드만 가진 객체
  function handleClick() {
    ref.current.focus();
    // This won't work because the DOM node isn't exposed:
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput placeholder="Enter your name" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

 

커스텀 메소드 정의

위의 예시에서는 DOM의 기본 메소드인 focus()를 부모 컴포넌트에 노출시켰는데 이처럼 기본 메소드를 그대로 사용할 수 있지만, 여러 동작을 조합한 커스텀 메소드를 만들어서 부모컴포넌트에서 호출할 수 있도록 노출시킬 수도 있다.

 

TypeScript 에서 사용할 때 타입 정의

타입스크립트와 함께 사용할 때는 useImperativeHandle 사용 시 실행할 메소드의 타입으로 정의해 주어야 한다.

// React v19

// useImperativeHandle로 실행할 scrollAndFocusAddComment 메소드 타입
type PostHandle = {
  scrollAndFocusAddComment: () => void
}

function Post({ ref }: { ref: Ref<PostHandle> }) {
  const commentsRef = useRef<CommentHandle>(null)
  const addCommentRef = useRef<HTMLInputElement>(null)

  useImperativeHandle(ref, () => {
    return {
      scrollAndFocusAddComment() {
        commentsRef.current?.scrollToBottom()
        addCommentRef.current?.focus()
      },
    }
  }, [])

  return (
    <>
      <article>
        <p>Welcome to my blog!</p>
      </article>
      <CommentList ref={commentsRef} />
      <AddComment ref={addCommentRef} />
    </>
  )
}

export default function Page() {
  const postRef = useRef<PostHandle>(null) 

  function handleClick() {
    postRef.current?.scrollAndFocusAddComment()
  }

  return (
    <>
      <button onClick={handleClick}>Write a comment</button>
      <Post ref={postRef} />
    </>
  )
}

 

주의 사항

유용한 점이 많은 useImperativeHandle hook이지만 남용해서 사용하면 안 된다.

  • 즉, props로 표현할 수 없는 필수적인 곳에만 ref를 사용하는 것을 React에서 권장하고 있는데, 예를 들어, 스크롤, 포커싱, 애니메이션 트리거, 텍스트 선택 등을 예로 들고 있다.
  • 같은 의미로 prop으로 표현할 수 있는 곳(ex. Modal의 open, close handle 등)에서는 isOpen state를 prop으로 사용하고, useEffect를 사용해서 필수적인 동작들을 정의할 수 있다. 

 

 


참고

React Docs