📍forwardRef
✅컴포넌트가 특정 DOM 노드를 ref를 가진 부모 컴포넌트에게 노출하게 해준다
즉, useRef로 만든 ref가 props로 타고 내려와 매개변수로 받아 지정할 DOM node를 활용하기 위한 기능
const SomeComponent = forwardRef(render)
📍forwardRef(render)
✅forwardRef를 호출하여 컴포넌트가 ref를 받아 자식 컴포넌트로 전달하게 한다
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
✅매개변수
render
- 컴포넌트를 위한 렌더링 콜백 함수 (가짜 컴포넌트 같은 역할, forwardRef로 래핑하여 완성)
- 부모 컴포넌트로부터 받은 props와 ref를 render 함수의 매개변수로 함
✅반환값
- forwardRef는 JSX로 렌더링할 수 있는 컴포넌트를 반환
- ref를 prop으로 받을 수 있다 (일반 컴포넌트는 불가)
✅주의사항
- strict mode 에서는 render 함수가 2번 호출됨 (development 환경에서만)
- render 함수가 순수함수라면 (그리고 순수함수이어야 함) 당연히 사이드 이펙트 없음
📍render 함수
forwardRef의 매개변수이며, render 함수는 props와 ref를 매개변수로 받음
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
✅매개변수
props
- 부모 컴포넌트로부터 전달받은 props
ref
- 부모 컴포넌트로부터 전달받은 ref 어트리뷰트 (부모 컴포넌트에서 useRef로 선언됨)
- ref는 객체이거나 함수일 수 있다
- 전달받은게 없다면, 기본값은 null
- 전달받은 대로 다른 컴포넌트에 전달해주면 됨
📍DOM 노드를 부모 컴포넌트에 노출시키기 예제
DOM 노드 => MyInput 컴포넌트가 리턴하는 input element
부모 컴포넌트 => App.js의 Form 컴포넌트
MyInput은 forwardRef로 만든 컴포넌트이므로 ref를 받을 수 있다
부모 컴포넌트에서 useRef로 만든 ref 를 받는 용도이다
MyInput.js
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} /> // ref를 받기 위해서 사용
</label>
);
});
export default MyInput;
App.js
import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null); // ref 선언
function handleClick() {
ref.current.focus(); // ref로 지정한 컴포넌트 focus하는 이벤트 핸들러
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} /> // ref 지정
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
📍비디오 재생 및 중지하기 예제
- 예제
video element는 자체 메서드로 play() 와 pause() 를 갖는다
App 컴포넌트와 video element가 있는 MyVideoPlayer 컴포넌트가 있다고 가정할 때,
App 컴포넌트에서 직접 video element에 접근하게 해보자
✅MyVideoPlayer.js
import { forwardRef } from 'react';
const VideoPlayer = forwardRef(function VideoPlayer({ src, type, width }, ref) {
return (
<video width={width} ref={ref}>
<source
src={src}
type={type}
/>
</video>
);
});
export default VideoPlayer;
video element (DOM 노드) 에 ref를 지정한다
✅App.js
import { useRef } from 'react';
import MyVideoPlayer from './MyVideoPlayer.js';
export default function App() {
const ref = useRef(null);
return (
<>
<button onClick={() => ref.current.play()}>
Play
</button>
<button onClick={() => ref.current.pause()}>
Pause
</button>
<br />
<MyVideoPlayer
ref={ref}
src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
type="video/mp4"
width="250"
/>
</>
);
}
- forwardRef로 만든 컴포넌트에 ref를 전달해줘야 한다
- useRef 로 만든 ref 객체를 여기서 조작할 수 있다
📍여러 컴포넌트 단계를 통해 ref 전달하기
예를 들어 App > FormField > MyInput 컴포넌트 3단계로 ref를 전달할 수 있다
- ref를 props로 받으려면, forwardRef로 만든 컴포넌트이어야 하므로,
FormField와 MyInput 컴포넌트 양쪽에서 forwardRef를 각각 사용하면 된다
📍useImperativeHandle 훅으로 forwardRef로 만든 컴포넌트에서 ref DOM 노드 제어하기
useImperativeHandle 훅을 사용하면 ref.current 이하의 메서드를 forwardRef 컴포넌트 레벨에서 정의할 수 있다
✅MyInput.js
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} />;
});
export default MyInput;
✅App.js
import { useRef } from 'react';
import MyInput from './MyInput.js';
export default function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<MyInput label="Enter your name:" ref={ref} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
📍ref 관련 주의사항
- ref를 과용하면 안된다
- props로 표현할 수 없는 명령형 동작에만 refs를 사용해야 한다.
예) 특정 노드로 스크롤 또는 포커스, 애니메이션 트리거, 텍스트 선택 등
📍참고
https://react-ko.dev/reference/react/forwardRef