forwardRef
는 컴포넌트가 ref와 함께 DOM 노드를 상위 컴포넌트에 노출할 수 있게 합니다.
const SomeComponent = forwardRef(render)
forwardRef()
를 호출하여 컴포넌트가 ref를 받고, 해당 ref를 자식 컴포넌트로 전달할 수 있게 합니다.
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
// ...
});
render
: 컴포넌트의 렌더 함수입니다. React는 이 함수를 부모로부터 받은 props와 ref와 함께 호출합니다. 반환하는 JSX는 컴포넌트의 출력이 됩니다.forwardRef
는 JSX에서 렌더링할 수 있는 React 컴포넌트를 반환합니다. 일반 함수로 정의된 React 컴포넌트와는 달리, forwardRef
에서 반환된 컴포넌트는 ref 프롭을 받을 수 있습니다.
forwardRef
는 렌더 함수를 인수로 받습니다. React는 이 함수를 props
와 ref
와 함께 호출합니다.
const MyInput = forwardRef(function MyInput(props, ref) {
return (
<label>
{props.label}
<input ref={ref} />
</label>
);
});
props
: 부모 컴포넌트에서 전달된 props입니다.ref
: 부모 컴포넌트에서 전달된 ref
속성입니다. ref
는 객체나 함수일 수 있습니다. 부모 컴포넌트에서 ref를 전달하지 않은 경우, ref는 null일 것입니다. 전달받은 ref
를 다른 컴포넌트에 전달하거나 useImperativeHandle
에 전달해야 합니다.forwardRef
는 JSX에서 렌더링할 수 있는 React 컴포넌트를 반환합니다. 일반 함수로 정의된 React 컴포넌트와는 달리, forwardRef
에서 반환된 컴포넌트는 ref
프롭을 전달받을 수 있습니다.
기본적으로 각 컴포넌트의 DOM 노드는 개인적입니다. 그러나 때로는 상위 컴포넌트에 DOM 노드를 노출하는 것이 유용할 수 있습니다. 예를 들어, 포커싱을 가능하게 하기 위해서입니다. 이를 위해 컴포넌트 정의를 forwardRef()
로 래핑하세요.
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} />
</label>
);
});
props 이후 두 번째 인수로 ref
를 받게 됩니다. 노출하려는 DOM 노드에 해당 ref를 전달하세요.
import { forwardRef } from 'react';
const MyInput = forwardRef(function MyInput(props, ref) {
const { label, ...otherProps } = props;
return (
<label>
{label}
<input {...otherProps} ref={ref} />
</label>
);
});
이를 통해 부모 Form
컴포넌트에서 MyInput
에 의해 노출된 <input> DOM 노드
에 접근할 수 있습니다.3
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>
);
}
이 Form
컴포넌트는 MyInput
에 ref를 전달합니다. MyInput
컴포넌트는 해당 ref를 브라우저의 <input>
태그로 전달합니다. 이 결과로, Form
컴포넌트는 해당 <input>
DOM 노드에 접근하고 focus()
를 호출할 수 있습니다.
컴포넌트 내부의 DOM 노드에 ref를 노출하면 나중에 컴포넌트의 내부를 변경하기가 더 어려워집니다. 일반적으로 버튼이나 텍스트 입력 같은 재사용 가능한 하위 컴포넌트에서 DOM 노드를 노출하지만, 아바타나 댓글 같은 응용 프로그램 수준의 컴포넌트에서는 그렇게 하지 않습니다.
DOM 노드로 ref
를 전달하는 대신, MyInput
과 같은 컴포넌트로 ref를 전달할 수 있습니다.
const FormField = forwardRef(function FormField(props, ref) {
// ...
return (
<>
<MyInput ref={ref} />
...
</>
);
});
만약 MyInput
컴포넌트가 그것의 <input>
으로 ref를 전달한다면, FormField
의 ref는 그 <input>
을 제공합니다.
function Form() {
const ref = useRef(null);
function handleClick() {
ref.current.focus();
}
return (
<form>
<FormField label="Enter your name:" ref={ref} isRequired={true} />
<button type="button" onClick={handleClick}>
Edit
</button>
</form>
);
}
Form
컴포넌트는 ref를 정의하고 FormField
에 전달합니다. FormField
컴포넌트는 이 ref를 MyInput
에 전달하고, MyInput
은 브라우저의 <input>
DOM 노드로 전달합니다. 이렇게 Form
은 그 DOM 노드에 접근합니다.
전체 DOM 노드를 노출하는 대신, 더 제한된 메서드 집합을 가진 사용자 정의 객체인 imperative handle을 노출할 수 있습니다. 이를 위해서는, DOM 노드를 보유할 별도의 ref를 정의해야 합니다.
const MyInput = forwardRef(function MyInput(props, ref) {
const inputRef = useRef(null);
// ...
return <input {...props} ref={inputRef} />;
});
전달받은 ref
를 useImperativeHandle
에 전달하고, ref
에 노출할 값을 지정합니다.
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} />;
});
만약 어떤 컴포넌트가 MyInput
의 ref를 받는다면, 해당 DOM 노드 대신 { focus, scrollIntoView }
객체만 받게 될 것입니다. 이렇게 함으로써 노출하는 DOM 노드에 대한 정보를 최소한으로 제한할 수 있습니다.
❗️ Pitfall
ref를 과도하게 사용하지 마십시오. ref는 프롭으로 표현할 수 없는 명령형 동작에만 사용해야 합니다. 예를 들어 노드로 스크롤링, 노드에 포커스, 애니메이션 트리거, 텍스트 선택 등입니다.
만약 프롭으로 표현할 수 있다면, ref를 사용해서는 안 됩니다. 예를 들어Modal
컴포넌트에서{ open, close }
와 같은 명령형 핸들을 노출하는 대신<Modal isOpen={isOpen}
/>과 같이isOpen
을 프롭으로 사용하는 것이 더 좋습니다. 효과(Effects)를 사용하면 명령형 동작을 프롭을 통해 노출할 수 있습니다.