9.1 useImperativeHandle이란?9.1.1 기본 구조9.1.2 사용시 주의점9.2 기본 사용법9.2.1 React.forwardRef9.2.2 useImperativeHandle 사용하기9.3 실용 예제9.3.1 useImperativeHandle을 사용하지 않은 실용 예제9.3.2 useImperativeHandle을 사용한 실용 예제
9.1 useImperativeHandle이란?
useImperativeHandle은 흔히 사용되지는 않지만 부모 컴포넌트가 자식 컴포넌트의 함수를 호출하거나 부모 컴포넌트에서 자식 컴포넌트의 상태 값을 변경하고 싶을 때 사용되는 Hook입니다. useImperativeHandle은 ref를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값을 커스터마이징할 수 있습니다. 이 Hook은 항상 ref와 함께 사용해야 하고 공식 문서에서는 React.forwardRef와 함께 사용하게끔 기재되어 있습니다.
9.1.1 기본 구조
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle은 위와 같이 세 개의 인자를 받을 수 있습니다. 첫 번째 인자는 프로퍼티를 부여할 ref입니다. 보통 forwardRef( ) 함수로 부모에게서 받아온 ref를 할당합니다. 두 번째 인자는 createHandle로 객체를 리턴하는 함수를 할당합니다. 해당 객체에는 추가하고 싶은 프로퍼티를 정의할 수 있습니다. 세 번째 인자는 의존성 배열로 필수가 아닌 선택적 인자이며 Hook이 재정의되어야 하는 조건들을 할당합니다.
9.1.2 사용시 주의점
useImperativeHandle은 자식 컴포넌트가 주요한 상태나 로직을 가지고 있는 경우에 유용하게 사용할 수 있는 Hook입니다.
대부분의 경우 ref를 사용한 명령형 코드는 피해야 합니다.
하지만 공식 문서에 기재되어 있듯이 '무엇'을 할 것인가? 에 초점을 맞춘 선언형 프로그래밍인 React는 선언적으로 해결될 수 있는 문제 (state나 prop을 사용해서 해결될 수 있는 문제)에서는 무엇을 '어떻게' 할 것인가?에 초점을 맞춘 명령형 프로그래밍인 ref를 사용하는 것을 권장하지 않습니다.
따라서 useImperativeHandle Hook은 필요한 곳에서만 선별적으로 사용해야 합니다.
9.2 기본 사용법
이제 useImperativeHandle을 사용하는 방법을 알아보겠습니다. 기본적으로 useImperativeHandle은 React.forwardRef와 함께 사용됩니다. 먼저 React.forwardRef에 대해 간단히 살펴보겠습니다.
9.2.1 React.forwardRef
React.forwardRef는 부모 컴포넌트가 자식 컴포넌트에게 ref를 전달할 때 사용하는 기술입니다. 부모 컴포넌트가 자식 컴포넌트의 DOM 노드에 직접 접근해야 할 때 특히 유용하게 사용됩니다. React 컴포넌트에서 ref를 prop으로 전달하기 위해서는 React.forwardRef를 사용해야 합니다.
간단한 예제로 React.forwardRef를 사용해보겠습니다. 그림 9-1과 같이 버튼을 클릭할 때
MyInput
이라는 Input 컴포넌트에 포커스를 주는 예제입니다. .png?table=block&id=f0f0ae8d-2f05-4aaf-81c2-8d3ee8389ed5&cache=v2)
먼저 부모 컴포넌트인
App
컴포넌트에 useRef를 이용해서 ref를 만들고 그것을 전달하기 위해 MyInput
의 ref 속성으로 넣어주었습니다. import React, { useRef } from 'react'; import MyInput from './MyInput'; function App() { const inputRef = useRef(); const focus = () => { inputRef.current.focus(); }; return ( <div style={{ margin: '20px 10px' }}> <MyInput ref={inputRef} /> <button style={{ marginLeft: '10px' }} onClick={focus}>인풋 포커스</button> </div> ); }; export default App;
ref를 전달받을 자식 컴포넌트를 forwardRef() 함수로 감싸주면 자식 컴포넌트는 두 번째 매개변수를 갖게 되는데, 여기에 부모에게서 전달받은 ref가 들어있습니다. 자식 컴포넌트는 전달받은 ref를 input 태그의 ref 속성으로 넣어주면 됩니다. 이제 ref는 input의 DOM 노드를 참조하게 되어 버튼이 클릭될 때마다 focus() 함수를 호출할 수 있게 됩니다.
import { forwardRef } from 'react'; function MyInput(props, ref) { return <input ref={ref} />; }; export default forwardRef(MyInput);
위와 같이 React.forwardRef를 사용하면 HTML 엘리먼트에 ref를 전달하듯 부모 컴포넌트가 자식 컴포넌트에게 ref를 전달할 수 있게 됩니다. 일반적으로 forwardRef() 함수는 애플리케이션 전체적으로 재사용성이 높은 최하위 컴포넌트를 대상으로 주로 사용되며, 그보다 상위 컴포넌트에서 사용하는 것은 권장되지 않습니다.
9.2.2 useImperativeHandle 사용하기
이제 useImperativeHandle을 React.forwardRef와 함께 사용해보겠습니다. useImperativeHandle을 사용하는 컴포넌트는 React.forwardRef로 감싸주어야 합니다. 그리고 부모 컴포넌트로부터 React.forwardRef의 두 번째 매개변수로 전달받은 ref를 useImperativeHandle의 첫 번째 인자 값으로 전달합니다.
위의 예제와 비슷하게 버튼을 클릭할 때
MyInput
이라는 Input 컴포넌트에 포커스를 주면서 동시에 input 태그의 value를 포커스 되었습니다!
라고 변경하는 예제를 살펴보겠습니다. 그림 9-2는 예제 코드의 실행 결과입니다.
자식 컴포넌트인
MyInput
를 보면, forwardRef() 함수로 감싸여 있고 두 번째 매개변수인 ref가 useImperativeHandle의 첫 번째 인자로 들어가 있는 것을 확인할 수 있습니다.import { forwardRef, useImperativeHandle, useRef } from 'react'; function MyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ customFocus: () => { inputRef.current.focus(); inputRef.current.value = '포커스 되었습니다!'; }, })); return <input ref={inputRef} />; } export default forwardRef(MyInput);
useImperativeHandle의 두 번째 인자에는
customFocus
라는 메서드를 정의했고, 사용하고 싶은 곳에서 inputRef.current에 customFocus
를 호출해서 사용할 수 있습니다.import React, { useRef } from 'react'; import MyInput from './MyInput'; function App() { const inputRef = useRef(); return ( <div style={{ margin: '20px 10px' }}> <MyInput ref={inputRef} /> <button style={{ marginLeft: '10px' }} onClick={() => inputRef.current.customFocus()} > 인풋 포커스 </button> </div> ); } export default App;
기존의 input 엘리먼트에는
customFocus
라는 메서드가 없지만, useImperativeHandle에서 정의했기 때문에 사용할 수 있습니다. 이처럼 useImperativeHandle Hook을 이용하면, 자식 컴포넌트에서 커스터마이징한 메서드를 부모 컴포넌트에서 사용할 수 있게 됩니다.9.3 실용 예제
9.3.1 useImperativeHandle을 사용하지 않은 실용 예제
useImperativeHandle의 실용 예제를 살펴보겠습니다. 아래 그림 9-3은 실용 예제의 완성본입니다. 실용 예제는 버튼을 클릭하면 CSS 박스 색상을 변경할 수 있는 애플리케이션입니다. useImperativeHandle Hook을 사용한 예제와 사용하지 않은 예제를 비교하여 Hook에 대해 더 자세히 다뤄보겠습니다.

우선은 useImperativeHandle Hook을 사용하지 않은 예제를 살펴보겠습니다.
import { useState } from "react"; import CustomModal from "./CustomModal"; function App() { const [open, setOpen] = useState(false); const [isTomato, setIsTomdato] = useState(false); const [isPink, setIsPink] = useState(false); const [isSky, setIsSky] = useState(false); return ( <> <button onClick={() => setOpen(true)}>Open</button> <button onClick={() => { setIsPink(true); }} > Pink Btn </button> <button onClick={() => { setIsSky(true); }} > Skyblue Btn </button> <button onClick={() => { setIsTomdato(true); }} > Tomato Btn </button> <CustomModal isOpen={open} onClose={() => { setOpen(false); setIsPink(false); setIsSky(false); setIsTomdato(false); }} isPink={isPink} isSky={isSky} isTomato={isTomato} /> </> ); } export default App;
자식 컴포넌트의 DOM 노드에 직접 접근이 불가능하니, 상태 값을 생성하여 자식 컴포넌트에 props 값으로 넘겨줍니다. 각 div 태그의 색상을 결정하기 위한 세 개의 상태 값, 모달을 여닫을 하나의 상태 값을 생성하여 전달합니다.
import React, { useRef } from "react"; import { useEffect } from "react"; const CustomModal = ({ isOpen, onClose, isPink, isSky, isTomato }) => { const closeRef = useRef(); const pinkRef = useRef(); const skyblueRef = useRef(); const tomatoRef = useRef(); useEffect(() => { if (isPink) { pinkRef.current.style = "width:50px; height:50px; background:pink;"; } if (isSky) { skyblueRef.current.style = "width:50px; height:50px; background:skyblue;"; } if (isTomato) { tomatoRef.current.style = "width:50px; height:50px; background:tomato;"; } }, [isPink, isSky, isTomato]); if (!isOpen) return null; return ( <div> <br /> <button ref={closeRef} onClick={onClose}> × </button> <h1>Color Change</h1> <div> <div ref={pinkRef}>color1</div> <div ref={skyblueRef}>color2</div> <div ref={tomatoRef}>color3</div> </div> </div> ); }; export default CustomModal;
CustomModal
컴포넌트에서는 각 div 태그에 접근하기 위한 Ref를 생성해줍니다. props로 넘겨받은 각 상태 값이 true로 바뀔 경우, Ref를 통해 해당 DOM 노드에 접근하여 스타일을 바꿔줍니다.위 코드는 부모 컴포넌트에서 useState를 내려주어 자식 컴포넌트 안에서 특정 태그에 영향을 주는 방법입니다. 하지만 다음에 나올 예제처럼 useImperativeHandle을 사용한다면, 부모에서 직접 자식 컴포넌트의 태그에 접근할 수 있습니다.
9.3.2 useImperativeHandle을 사용한 실용 예제
useImperativeHandle Hook을 사용하여 앞서 만든 실용 예제와 같은 애플리케이션을 만들어보겠습니다.
import { useState, useRef } from "react"; import CustomModal from "./CustomModal"; function App() { const [open, setOpen] = useState(false); const modalRef = useRef(); return ( <> <button onClick={() => setOpen(true)}>Open</button> <button onClick={() => { //작업내용 }} > Pink Btn </button> <CustomModal ref={modalRef} open={open} onClose={() => setOpen(false)} /> </> ); } export default App;
먼저 App.js에서 Open 버튼과 Pink Btn 버튼을 만들었습니다. Open 버튼을 클릭하면 Color Change 모달이 나타나고 Pink Btn 버튼을 클릭하면 ‘color1’ 텍스트의 배경색이 분홍색으로 변경됩니다. 해당 기능을 모달에 해당하는
CustomModal
컴포넌트에서 useImperativeHandle Hook을 활용해 구현하겠습니다. useImperativeHandle Hook을 사용하기 위해
CustomModal
컴포넌트에 modalRef
를 prop으로 전달해주었습니다. 아래 그림 9-4는 Open 버튼을 클릭하여 모달을 띄운 상태입니다.
import React from "react"; import { useRef } from "react"; function CustomModal({ open, onClose }, ref) { const closeRef = useRef(); const pinkRef = useRef(); if (!open) return null; return ( <div> <br /> <button ref={closeRef} onClick={onClose}> × </button> <h1>Color Change</h1> <div> <div ref={pinkRef}>color1</div> </div> </div> ); } export default React.forwardRef(CustomModal);
CustomModal
컴포넌트를 살펴보겠습니다. useRef Hook을 통해 closeRef와 pinkRef라는 ref 객체를 만듭니다. 두 가지 ref 객체를 각각 모달의 닫기 버튼과 ‘color1’ 텍스트의 ref 값으로 설정합니다. 이는 닫기 버튼과 ‘color1’ 텍스트의 DOM 노드를 가리켜 원하는 방식으로 조작하기 위한 목적입니다. useImperativeHandle(ref, () => { return { closeBtn: closeRef.current, pinkBtn: pinkRef.current, }; });
이후 useImperativeHandle Hook의 인자로 App.js에서 전달해온 prop인 ref를 받습니다. 그 다음 콜백함수로 closeBtn과 pinkBtn을 반환하면 부모 컴포넌트인 App.js에서도 해당 객체를 사용할 수 있습니다. closeBtn에 closeRef.current을 할당하여 닫기 버튼 DOM 노드를 가리킬 수 있게 하고, pinkBtn에는 pinkRef.current 을 넣어 ‘color1’ 텍스트의 div 태그를 가리킬 수 있게 합니다. 이를 통해 App.js에서 closeBtn과 pinkBtn을 활용하여 모달 닫기 기능과 배경색 변경 기능을 구현할 준비가 되었습니다.
import React from "react"; import { useRef, useImperativeHandle } from "react"; function CustomModal({ open, onClose }, ref) { const closeRef = useRef(); const pinkRef = useRef(); useImperativeHandle(ref, () => { return { closeBtn: closeRef.current, pinkBtn: pinkRef.current, }; }); if (!open) return null; return ( <div> <br /> <button ref={closeRef} onClick={onClose}> × </button> <h1>Color Change</h1> <div> <div ref={pinkRef}>color1</div> </div> </div> ); } export default React.forwardRef(CustomModal);
위 코드는 useImperativeHandle Hook을 포함한
CustomModal
컴포넌트의 전체 코드입니다. ref를 전달받는 자식 컴포넌트인 CustomModal
에서 forwardRef() 함수로 감싼 후 export해야 useImperativeHandle Hook의 인자로 ref를 전달받을 수 있기 때문에 마지막 코드에 export default React.forwardRef(CustomModal)를 넣어줍니다. <button onClick={() => { modalRef.current.pinkBtn.style = "width:50px; height:50px; background:pink;"; }} > Pink Btn </button>;
마지막으로 App.js로 돌아와 Pink Btn 버튼 onClick 이벤트 핸들러에 modalRef.current.pinkBtn.style을 넣어 css 스타일을 변경해줍니다. width:50px; height:50px; background:pink;의 값을 설정해 너비, 높이와 배경색을 변경해주었습니다. 아래 그림 9-5는 Pink Btn을 클릭하여 ‘color1’ 텍스트의 스타일을 바꾼 결과입니다.

위와 동일한 방식으로 모달의 닫기 버튼, Skyblue Btn과 Tomato Btn을 만들어 원래 만들고자 했던 그림 9-1과 같은 애플리케이션을 완성합니다. 아래는 최종 코드입니다.
import { useState, useRef } from "react"; import CustomModal from "./CustomModal"; function App() { const [open, setOpen] = useState(false); const modalRef = useRef(); return ( <> <button onClick={() => setOpen(true)}>Open</button> <button onClick={() => { modalRef.current.pinkBtn.style = "width:50px; height:50px; background:pink;"; }} > Pink Btn </button> <button onClick={() => { modalRef.current.skyblueRefBtn.style = "width:50px; height:50px; background:skyblue;"; }} > Skyblue Btn </button> <button onClick={() => { modalRef.current.tomatoBtn.style = "width:50px; height:50px; background:tomato;"; }} > Tomato Btn </button> <CustomModal ref={modalRef} open={open} onClose={() => setOpen(false)} /> </> ); } export default App;
import React from "react"; import { useRef, useImperativeHandle } from "react"; function CustomModal({ open, onClose }, ref) { const closeRef = useRef(); const pinkRef = useRef(); const skyblueRef = useRef(); const tomatoRef = useRef(); useImperativeHandle(ref, () => { return { closeBtn: closeRef.current, pinkBtn: pinkRef.current, skyblueRefBtn: skyblueRef.current, tomatoBtn: tomatoRef.current, }; }); if (!open) return null; return ( <div> <br /> <button ref={closeRef} onClick={onClose}> × </button> <h1>Color Change</h1> <div> <div ref={pinkRef}>color1</div> <div ref={skyblueRef}>color2</div> <div ref={tomatoRef}>color3</div> </div> </div> ); } export default React.forwardRef(CustomModal);