useImperativeHandle은 실제 개발 과정에서는 자주 볼 수 없는 훅으로 널리 사용되지는 않습니다. 그럼에도 useImperativeHandle은 일부 사용 사례에서 유용하게 활용될 수 있습니다. useImperativeHandle을 이해하기 위해서는 먼저 React.forwardRef에 대해 알아야 합니다.
forwardRef란?
ref는 useRef에서 반환한 객체로, 리액트 컴포넌트의 props인 ref에 넣어 HTMLElement에 접근하는 용도로 흔히 사용됩니다. key와 마찬가지로 ref도 리액트에서 컴포넌트의 props로 사용할 수 있는 예약어로서 별도로 선언돼 있지 않아도 사용할 수 있습니다. 만약 이러한 ref를 상위 컴포넌트에서 하위 컴포넌트로 전달하고 싶다면 어떻게 해야 할까요? 즉, 상위 컴포넌트에서는 접근하고 싶은 ref가 있지만 이를 직접 Props로 넣어 사용할 수 없을 때는 어떻게 해야 할까요?
import React, { useEffect, useRef } from 'react';
function ChildComponent({ ref }) {
useEffect(() => {
// undefined
console.log(ref);
}, [ref]);
return <p>안녕, 코딩병원</p>
}
export default function ParentComponent() {
const inputRef = useRef();
return (
<>
<input ref={inputRef} />
<ChildComponent ref={inputRef} />
</>
)
}
위의 코드를 실행하면 리액트에서 ref는 props로 사용할 수 없다는 경고문과 함께 접근을 시도할 경우 undefiend를 반환한다고 아래와 같은 경고문이 나옵니다.
ParentComponent.jsx:3 Warning: ChildComponent: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop.
그렇다면 예약어로 지정된 ref 대신 다른 props로 받으면 어떻게 될까요?
import React, { useEffect, useRef } from 'react';
function ChildComponent({ parentRef }) {
useEffect(() => {
// { curent: HTMLInputElement }
console.log(parentRef);
}, [parentRef]);
return <p>안녕, 코딩병원</p>
}
export default function ParentComponent() {
const inputRef = useRef();
return (
<>
<input ref={inputRef} />
<ChildComponent parentRef={inputRef} />
</>
)
}
이러한 방식은 앞선 예제와 다르게 잘 작동합니다. 그리고 이는 클래스 컴포넌트나 함수 컴포넌트에서도 동일하게 작동합니다. forwardRef는 방금 작성한 코드와 동일한 작업을 하는 리액트 API입니다. 그런데 단순히 이렇게 props로 구현할 수 있는 것을 왜 만들었을까요?
forwardRef가 탄생한 배경은 ref를 전달하는 데 있어서 일관성을 제공하기 위해서입니다. 어떤 props명으로 전달할지 모르고, 이에 대한 완전한 네이밍의 자유가 주어진 props보다는 forwardRef를 사용하면 좀 더 확실하게 ref를 전달할 것임을 예측할 수 있고, 또 사용하는 쪽에서도 확실히 안정적으로 받아서 사용할 수 있습니다. forwardRef를 사용하는 다음 코드를 살펴보겠습니다.
import React, { useEffect, useRef, forwardRef } from 'react';
const ChildComponent = forwardRef((props, ref) => {
useEffect(() => {
// { curent: HTMLInputElement }
console.log(ref);
}, [ref]);
return <p>안녕, 코딩병원</p>
})
export default function ParentComponent() {
const inputRef = useRef();
return (
<>
<input ref={inputRef} />
<ChildComponent ref={inputRef} />
</>
)
}
먼저 ref를 받고자 하는 컴포넌트를 forwardRef로 감싸고, 두 번째 인수로 ref를 전달받습니다. 그리고 부모 컴포넌트에서는 동일하게 props.ref를 통해 ref를 넘겨주면 됩니다. 이렇게 forwardRef를 사용하는 코드로 수정하면 ref를 props로 전달할 수 있고, 전달받은 컴포넌트에서도 ref라는 이름을 그대로 사용할 수 있습니다.
useImperativeHandle이란?
forwardRef에 대해 알아봤으니 useImperativeHandle에 대해 살펴보겠습니다. useImperativeHandle은 부모에게서 넘겨받은 ref를 원하는 대로 수정할 수 있는 훅입니다. 다음 코드를 통해 알아보겠습니다.
import { useEffect, useRef, forwardRef, useImperativeHandle, useState } from 'react';
const ChildComponent = forwardRef((props, ref) => {
useEffect(() => {
// { curent: HTMLInputElement }
console.log(ref);
}, [ref]);
return <p>안녕, 코딩병원</p>
})
const Input = forwardRef((props, ref) => {
useImperativeHandle(
ref,
() => ({
alert: () => alert(props.value)
}),
[props.value]
)
return <input ref={ref} {...props} />
})
export default function ParentComponent() {
const inputRef = useRef();
const [text, setText] = useState('');
function handleOnClick() {
inputRef.current.alert();
}
function handleChange(e) {
setText(e.target.value)
}
return (
<>
<Input ref={inputRef} value={text} onChange={handleChange} />
<button onClick={handleOnClick}>Focus</button>
</>
)
}
'useImperativeHandle을 사용하면 부모 컴포넌트에서 노출되는 값을 원하는 대로 바꿀 수 있다'라는 말의 뜻이 명확해졌습니다. 원래 ref는 {current: <HTMLElement>}와 같은 형태로 HTMLElement만 주입할 수 있는 객체였습니다. 그러나 여기서는 전달받은 ref에다 useImperativeHandle 훅을 사용해 추가적인 동작을 정의했습니다. 이로써 부모는 단순히 HTMLElement뿐만 아니라 자식 컴포넌트에서 새롭게 설정한 객체의 키와 값에 대해서도 접근할 수 있게 되었습니다. useImperativeHandle을 사용하면 이 ref의 값에 원하는 값이나 액션을 정의할 수 있습니다.
References
https://react-ko.dev/reference/react/useImperativeHandle
React DeepDive
'1. 웹개발 > 1_1_5 React JS' 카테고리의 다른 글
[React] Flux 패턴과 Elm 아키텍처 (0) | 2024.05.18 |
---|---|
[React] 성능을 측정하는 지표 (LCP, FID, CLS, FCP, TTFB 등) (0) | 2024.05.11 |
[React] 커스텀훅 vs 고차 컴포넌트 (0) | 2024.04.27 |
[React] useLayoutEffet와 useEffect (0) | 2024.04.20 |
[React] useReducer는 언제 사용할까? (0) | 2024.04.06 |