일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- pure functions
- useref 타입
- express react
- 특정 dom node만 노출시키고 싶을 때
- 스프린트 여정 마침
- next.js
- node.js(express) + ws(websocket) + react
- 리스티웨이브
- 티스토리챌린지
- useimperativehandle 사용할때
- React
- usemutation custom hook
- 리액트 19 useref
- 오블완
- CSS module 장점
- express로 채팅 기능 구현하기
- useimperativehandle 사용법
- 회고
- trigger additional callbacks
- http.createserver
- inferred type error
- app.listen
- components as formulas
- 코드잇 스프린트 FE 1기
- 리액트로 채팅 기능 구현하기
- 이미지 업로드 과정
- css module classNames
- dynamic metadata
- type reference cannot be named error
- 프리렌더링
- Today
- Total
Life is connecting the dots .
useImperativeHandle은 언제 사용할까? 본문
최근 프로젝트를 하면서 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를 사용해서 필수적인 동작들을 정의할 수 있다.
참고
'Programming > React' 카테고리의 다른 글
(React-Query) useMutation 로직 커스텀 훅으로 리팩토링하기 (0) | 2024.11.28 |
---|---|
Zustand를 선택한 이유 (0) | 2024.11.07 |
React 컴포넌트를 순수 함수로 작성하는 이유 (0) | 2024.04.02 |
이미지 업로드 기능 사용성[UI/UX] 개선 (0) | 2023.09.01 |
React Query - 서버 상태 관리 라이브러리 (0) | 2023.08.11 |