useImperativeHandle

김동현·2026년 3월 17일

useImperativeHandle

소개

useImperativeHandleref로 노출되는 핸들을 커스터마이즈할 수 있게 해주는 React Hook이에요.

useImperativeHandle(ref, createHandle, dependencies?)

목차


레퍼런스 {/reference/}

useImperativeHandle(ref, createHandle, dependencies?) {/useimperativehandle/}

컴포넌트의 최상위 레벨에서 useImperativeHandle을 호출해서 노출할 ref 핸들을 커스터마이즈하세요:

import { useImperativeHandle } from 'react';

function MyInput({ ref }) {
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
    };
  }, []);
  // ...

아래에서 더 많은 예시를 확인하세요.

매개변수 {/parameters/}

  • ref: MyInput 컴포넌트에 prop으로 전달받은 ref예요.

  • createHandle: 인자를 받지 않고, 노출하고 싶은 ref 핸들을 반환하는 함수예요. 이 ref 핸들은 어떤 타입이든 될 수 있어요. 보통은 노출하고 싶은 메서드들을 담은 객체를 반환하게 돼요.

  • 선택적 dependencies: createHandle 코드 안에서 참조하는 모든 반응형 값들의 목록이에요. 반응형 값에는 props, state, 그리고 컴포넌트 본문 안에서 직접 선언된 모든 변수와 함수가 포함돼요. 린터가 React용으로 설정되어 있다면, 모든 반응형 값이 의존성으로 올바르게 지정되었는지 검증해줄 거예요. 의존성 목록은 항목 수가 일정해야 하고, [dep1, dep2, dep3] 처럼 인라인으로 작성해야 해요. React는 각 의존성을 이전 값과 Object.is 비교를 사용해서 비교해요. 리렌더링으로 인해 어떤 의존성이 변경되었거나, 이 인자를 생략했다면, createHandle 함수가 다시 실행되고 새로 생성된 핸들이 ref에 할당돼요.

💡 참고

React 19부터 ref를 prop으로 사용할 수 있어요. React 18 이전 버전에서는 forwardRef를 통해 ref를 받아야 했어요.

부연설명을 하자면, 예전에는 자식 컴포넌트에서 ref를 받으려면 반드시 forwardRef로 컴포넌트를 감싸야 했는데, React 19부터는 그냥 일반 prop처럼 ref를 받을 수 있게 되어서 훨씬 간편해졌어요.

반환값 {/returns/}

useImperativeHandleundefined를 반환해요.


사용법 {/usage/}

부모 컴포넌트에 커스텀 ref 핸들 노출하기 {/exposing-a-custom-ref-handle-to-the-parent-component/}

부모 요소에 DOM 노드를 노출하려면, ref prop을 해당 노드에 전달하면 돼요.

function MyInput({ ref }) {
  return <input ref={ref} />;
};

위 코드에서는 MyInput에 대한 ref가 <input> DOM 노드를 받게 돼요. 하지만 대신 커스텀 값을 노출할 수도 있어요. 노출되는 핸들을 커스터마이즈하려면, 컴포넌트의 최상위 레벨에서 useImperativeHandle을 호출하세요:

import { useImperativeHandle } from 'react';

function MyInput({ ref }) {
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
    };
  }, []);

  return <input />;
};

위 코드에서 ref가 더 이상 <input>에 전달되지 않는다는 점을 주목하세요.

예를 들어, <input> DOM 노드 전체를 노출하고 싶지는 않지만, 그 중에서 focusscrollIntoView 두 가지 메서드만 노출하고 싶다고 해볼게요. 이렇게 하려면, 실제 브라우저 DOM을 별도의 ref에 보관하세요. 그리고 나서 useImperativeHandle을 사용해서 부모 컴포넌트가 호출할 수 있는 메서드만 가진 핸들을 노출하세요:

import { useRef, useImperativeHandle } from 'react';

function MyInput({ ref }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

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

이제 부모 컴포넌트가 MyInput에 대한 ref를 얻으면, focusscrollIntoView 메서드를 호출할 수 있어요. 하지만 기저에 있는 <input> DOM 노드에 대한 전체 접근 권한은 갖지 못해요.

💡 부연설명: 이게 왜 유용하냐면, 컴포넌트의 내부 구현을 캡슐화할 수 있기 때문이에요. 부모 컴포넌트가 자식의 DOM 노드에 직접 접근해서 스타일을 마음대로 바꾸거나 예상치 못한 조작을 하는 것을 방지할 수 있죠. 꼭 필요한 메서드만 골라서 노출하는 거예요.

// App.js
import { useRef } from 'react';
import MyInput from './MyInput.js';

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

  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>
  );
}
// src/MyInput.js
import { useRef, useImperativeHandle } from 'react';

function MyInput({ ref, ...props }) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

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

export default MyInput;
input {
  margin: 5px;
}

자체 명령형 메서드 노출하기 {/exposing-your-own-imperative-methods/}

명령형 핸들을 통해 노출하는 메서드가 반드시 DOM 메서드와 정확히 일치할 필요는 없어요. 예를 들어, 이 Post 컴포넌트는 명령형 핸들을 통해 scrollAndFocusAddComment 메서드를 노출해요. 이렇게 하면 부모인 Page가 버튼을 클릭했을 때 댓글 목록을 스크롤하고 동시에 입력 필드에 포커스를 맞출 수 있어요:

// App.js (Page)
import { useRef } from 'react';
import Post from './Post.js';

export default function Page() {
  const postRef = useRef(null);

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

  return (
    <>
      <button onClick={handleClick}>
        Write a comment
      </button>
      <Post ref={postRef} />
    </>
  );
}
// src/Post.js
import { useRef, useImperativeHandle } from 'react';
import CommentList from './CommentList.js';
import AddComment from './AddComment.js';

function Post({ ref }) {
  const commentsRef = useRef(null);
  const addCommentRef = useRef(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 Post;
// src/CommentList.js
import { useRef, useImperativeHandle } from 'react';

function CommentList({ ref }) {
  const divRef = useRef(null);

  useImperativeHandle(ref, () => {
    return {
      scrollToBottom() {
        const node = divRef.current;
        node.scrollTop = node.scrollHeight;
      }
    };
  }, []);

  let comments = [];
  for (let i = 0; i < 50; i++) {
    comments.push(<p key={i}>Comment #{i}</p>);
  }

  return (
    <div className="CommentList" ref={divRef}>
      {comments}
    </div>
  );
}

export default CommentList;
// src/AddComment.js
import { useRef, useImperativeHandle } from 'react';

function AddComment({ ref }) {
  return <input placeholder="Add comment..." ref={ref} />;
}

export default AddComment;
.CommentList {
  height: 100px;
  overflow: scroll;
  border: 1px solid black;
  margin-top: 20px;
  margin-bottom: 20px;
}

💡 부연설명: 위 예시를 보면, Post 컴포넌트는 scrollAndFocusAddComment라는 완전히 커스텀한 메서드를 노출하고 있어요. 이 메서드는 내부적으로 CommentListscrollToBottom()AddCommentfocus()를 동시에 호출하죠. 이런 식으로 여러 자식 컴포넌트의 동작을 하나의 메서드로 조합해서 부모에게 노출할 수 있어요. DOM 메서드에 국한되지 않고, 여러분이 원하는 어떤 동작이든 조합해서 만들 수 있다는 게 핵심이에요.

⚠️ 주의

ref를 남용하지 마세요. ref는 props로 표현할 수 없는 명령형 동작에만 사용해야 해요: 예를 들어, 노드로 스크롤하기, 노드에 포커스 맞추기, 애니메이션 트리거하기, 텍스트 선택하기 등이요.

무언가를 prop으로 표현할 수 있다면, ref를 사용하면 안 돼요. 예를 들어, Modal 컴포넌트에서 { open, close } 같은 명령형 핸들을 노출하는 대신에, <Modal isOpen={isOpen} /> 처럼 isOpen을 prop으로 받는 게 더 나아요. Effect를 사용하면 props를 통해 명령형 동작을 노출하는 데 도움이 될 수 있어요.

부연설명을 좀 더 하자면, React의 핵심 철학은 선언적(declarative) 프로그래밍이에요. 가능하면 "이렇게 해라"(명령형)가 아니라 "이런 상태일 때 이렇게 보여라"(선언형)로 표현하는 게 좋아요. ref와 useImperativeHandle은 정말 필요한 경우에만 사용하는 탈출구(escape hatch)라고 생각하면 돼요.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글