리액트에서 부모 컴포넌트가 자식 컴포넌트의 HTML요소를 ref로 지정하고, 해당 HTML요소에 접근하여 이벤트를 다루는 방법에 대해 공부한 내용을 정리해보고자 한다.
리액트에서 ref을 prop로 사용하려면 React에서 제공하는 forwardRef를 사용해야한다.
const Parent =()=>{
const inputRef= useRef<HTMLInputElement>(null);
//....
return(
//...
<MyInput ref={inputRef}/>
)
}
import React from 'react'
// forwardRef 함수에 제네릭으로 ref와 props의 타입을 명시
// ref type :HTMLInputElement
// props type :React.InputHTMLAttributes<HTMLInputElement>
const MyInput = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
(props, ref) => {
// input 요소에 ref와 props를 적용하여 반환
return <input ref={ref} {...props} />
}
)
const MyInput = React.forwardRef<HTMLInputElement>>(
(_, ref) => ...
useImperativeHandle 은 리액트 훅 중 하나로, 부모 컴포넌트에게 노출할 ref 핸들을 사용자가 직접 정의할 수 있게 (ref로 노출 시키는 노드의 일부 메서드만 노출할 수 있게) 해주는 훅이다.
useImperativeHandle는 부모 컴포넌트가 ref를 핸들하는 것을 정의하는 것이기 때문에 부모 컴포넌트가 ref에 접근할 수 있도록 해주는 forwardRef와 함께 사용해야한다.
import { forwardRef, useImperativeHandle, useRef } from 'react';
const MyInput = forwardRef((props, ref)=> {
const inputRef = useRef();
useImperativeHandle(ref, () => {
return {
focus: (f) =>{
if(f) inputRef.current?.focus();
},
scrollIntoView: () => {
inputRef.current?.scrollIntoView();
}
};
}, []);
return <input {...props} ref={inputRef} />;
});
const Parent =()=>{
const inputRef= useRef<InputHandle>(null);
//....
const handleFocus =(f)=>{
inputRef.current?.focus(f);
};
const scrollIntoView =()=>{
inputRef.current?.scrollIntoView()}
return(
//...
<MyInput ref={inputRef}/>
<button onClick={()=>handleFocus(true)}>focus</button>
<button onClick={()=>handleFocus(false)}>unFocus</button>
<button onClick={scrollIntoView}>focus</button>
)
}
import { forwardRef, useImperativeHandle, useRef } from 'react';
export type InputHandle ={
focus:(f:boolean)=>void,
scrollIntoView :()=>void,
}
const MyInput = forwardRef<InputHandle,React.InputHTMLAttributes<HTMLInputElement> >((props, ref)=> {
const inputRef = useRef();
useImperativeHandle(ref, () => {
return {
focus: (f:boolean) => {
if(f) inputRef.current?.focus();
},
scrollIntoView: () => {
inputRef.current?.scrollIntoView();
},
};
}, []);
return <input {...props} ref={inputRef} />;
});
const Parent =()=>{
const inputRef= useRef<InputHandle>(null);
//....
const
return(
//...
<MyInput ref={inputRef}/>
<button onClick={()=>handleFocus(true)}>focus</button>
<button onClick={()=>handleFocus(false)}>unFocus</button>
<button onClick={scrollIntoView}>focus</button>
)
}
⚠️typescript에서 useImperativeHandle을 사용 시 주의할 점은 js에서 사용할때와 다르기 ref에 대한 타입을 HTMLElement가 아니라 useImperativeHandle로 지정하는 메서드를 속성으로 가지는 타입(ex:InputHandle)으로 지정해주어야한다.