forwardRef를 사용하면 컴포넌트가 ref를 사용하여 부모 컴포넌트에 DOM 노드를 노출할 수 있음.
const SomeComponent = forwardRef(render)
컴포넌트가 ref를 받아서 하위 컴포넌트로 전달하도록 하려면 forwardRef()를 호출:
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를 전달하지 않은 경우 null이 됨. 받은 ref를 다른 컴포넌트에 전달하거나 useImperativeHandle에 전달해야 함.
JSX에서 렌더링할 수 있는 React 컴포넌트를 반환함. 일반 함수로 정의된 React 컴포넌트와 달리, forwardRef가 반환하는 컴포넌트는 ref prop을 받을 수 있음.
기본적으로 각 컴포넌트의 DOM 노드는 private 함. 그러나 때로는 포커싱을 허용하는 등 부모에게 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 노드에 전달:
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 노드에 액세스할 수 있음:
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 노드를 노출하지만 아바타나 코멘트와 같은 애플리케이션 레벨 컴포넌트에서는 노출하지 않음.
Ref를 DOM 노드로 전달하는 대신 MyInput과 같은 자체 컴포넌트로 전달할 수 있음:
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) 핸들이라고 하는 커스텀 객체를 보다 제한된 메서드 집합과 함께 노출할 수 있음. 이렇게 하려면 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를 과도하게 사용하지 말 것. 노드로 스크롤하기, 노드에 포커스 맞추기, 애니메이션 트리거하기, 텍스트 선택하기 등과 같이 props로 표현할 수 없는 명령형 동작에만 ref를 사용해야함.
Props로 표현할 수 있는 것이 있다면 ref를 사용해서는 안됨. 예를 들어, 모달 컴포넌트에서
{ open, close }와 같은 명령형 핸들을 노출하는 대신,<Modal isOpen={isOpen} />처럼isOpen을 prop으로 받는 것이 더 좋음. Effect는 props를 통해 명령형 동작을 노출하는 데 도움이 될 수 있음.