ref로 노출된 handle을 커스터마이징할 수 있는 React Hook
useImperativeHandle(ref, createHandle, dependencies?)
컴포넌트의 최상위 레벨에서 useImperativeHandle을 호출하여 컴포넌트가 노출하는 ref handle을 커스터마이즈:
import { forwardRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
useImperativeHandle(ref, () => {
return {
// ... your methods ...
};
}, []);
// ...
ref: forwardRef 렌더링 함수에서 두 번째 인수로 받은 ref.
createHandle: 인수를 받지 않고, 노출하려는 ref handle을 반환하는 함수. 이 ref handle은 어떤 타입이든 가질 수 있음. 일반적으로 노출하려는 메서드가 있는 객체를 반환.
dependencies (optional): createHandle 코드 내부에서 참조된 모든 반응형 값의 목록. 반응형 값에는 props, state, 컴포넌트 본문에 직접 선언된 모든 변수와 함수가 포함됨. Linter가 React용으로 구성된 경우, 모든 반응형 값이 dependency로 올바르게 지정되었는지 확인함. Dependencies 목록에는 일정한 수의 항목이 있어야 하며 [dep1, dep2, dep3]과 같이 인라인으로 작성해야함. React는 Object.is 비교를 사용하여 각 dependency를 이전 값과 비교함. 재렌더링으로 인해 일부 dependency가 변경되었거나 이 인수를 생략한 경우, createHandle 함수가 다시 실행되고 새로 생성된 handle이 ref에 할당됨.
undefined를 반환함.
기본적으로 컴포넌트는 부모 컴포넌트에 DOM 노드를 노출하지 않음. 예를 들어, MyInput의 부모 컴포넌트가 <input> DOM 노드에 액세스하도록 하려면 forwardRef를 사용해야함:
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
return <input {...props} ref={ref} />;
});
위 코드를 사용하면 MyInput에 전달된 ref가 <input> DOM 노드를 받게 됨. 하지만 대신 커스텀 값을 노출할 수도 있음. 노출된 handle을 커스터마이즈하려면 컴포넌트의 최상위 수준에서 useImperativeHandle을 호출:
import { forwardRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
useImperativeHandle(ref, () => {
return {
// ... your methods ...
};
}, []);
return <input {...props} />;
});
위 코드에서는 ref가 더 이상 <input>으로 전달되지 않음.
예를 들어, 전체 <input> DOM 노드를 노출하고 싶지 않지만 그 중 두 가지 메서드인 focus와 scrollIntoView만 노출하고 싶다면, 실제 브라우저 DOM을 별도의 ref에 보관한 후 useImperativeHandle을 사용하여 부모 컴포넌트가 호출할 메서드만 있는 handle을 노출할 것:
import { forwardRef, useRef, useImperativeHandle } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => {
return {
focus() {
inputRef.current.focus();
},
scrollIntoView() {
inputRef.current.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
이제 부모 컴포넌트가 MyInput에 대한 ref를 받으면, 이 컴포넌트에서focus와 scrollIntoView 메서드를 호출할 수 있지만 기본 <input> DOM 노드에 대한 전체 액세스 권한은 갖지 못함.
명령형(imperative) handle을 통해 노출하는 메서드가 DOM 메서드와 정확히 일치할 필요는 없음. 예를 들어, 아래 예제의 Post 컴포넌트는 명령형(imperative) handle을 통해 scrollAndFocusAddComment 메서드를 노출함. 이를 통해 사용자가 버튼을 클릭하면 상위 Page에서 댓글 목록을 스크롤하고 입력 필드에 초점을 맞출 수 있음:
// App.js
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} />
</>
);
}
import { forwardRef, useRef, useImperativeHandle } from 'react';
import CommentList from './CommentList.js';
import AddComment from './AddComment.js';
const Post = forwardRef((props, 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;
Pitfall
Ref를 과도하게 사용하지 말 것. 노드로 스크롤하기, 노드에 포커스 맞추기, 애니메이션 트리거하기, 텍스트 선택하기 등과 같이 props로 표현할 수 없는 명령형 동작에만 ref를 사용해야함.
Props로 표현할 수 있는 것이 있다면 ref를 사용해서는 안됨. 예를 들어, 모달 컴포넌트에서
{ open, close }와 같은 명령형 핸들을 노출하는 대신,<Modal isOpen={isOpen} />처럼isOpen을 prop으로 받는 것이 더 좋음. Effect는 props를 통해 명령형 동작을 노출하는 데 도움이 될 수 있음.