forwardRef 란?
forwardRef는 React에서 제공하는 함수로, 컴포넌트가 부모로부터 전달된 ref를 하위 DOM 요소나 다른 컴포넌트에 전달할 수 있도록 도와준다. 기본적으로 React에서 함수형 컴포넌트가 ref를 직접 받을 수 없기 때문에, 이를 해결하기 위해 forwardRef를 사용한다.
함수형 컴포넌트가 ref를 직접 받을 수 없는 이유
React에서 ref는 기본적으로 DOM 요소나 클래스형 컴포넌트를 참조하도록 설계되었다. 그러나 함수형 컴포넌트는 일반 함수처럼 동작하며, React에서는 이를 상태가 없는 "순수 컴포넌트"로 취급한다.
ref의 역할은 특정 DOM 노드 또는 클래스형 컴포넌트의 인스턴스를 가르키는 것이다. 하지만 함수형 컴포넌트는 다음과 같은 이유로 ref를 직접 받을 수 없다.
- 함수형 컴포넌트는 React에서 별도의 인스턴스를 생성하지 않는다.
- 함수형 컴포넌트 자체는 함수 호출로만 처리되며, React는 이를 "렌더링 결과"로 다룬다.
함수형 컴포넌트는 상태와 생명주기가 없다. 클래스형 컴포넌트는 내부적으로 인스턴스(this)를 생성하며, React가 이 인스턴스를 참조로 다룰 수 있다. 반면, 함수형 컴포넌트는 인스턴스가 없으므로, 참조 대상이 될 수 없다.
애초에 함수형 컴포넌트는 단순히 props를 받아 UI를 반환하는 순수한 함수로 설계가 되었다. 따라서 함수형 컴포넌트는 렌더링의 결과를 반환하는 역할이고 참조할 인스턴스가 필요하지 않는다.
그러나, React 16.8부터 useRef, useState, useEffect 등 다양한 훅이 추가되면서 함수형 컴포넌트에서도 상태 관리와 DOM 참조가 가능해졌다. 그러나 ref를 직접 전달하려면 여전히 forwardRef가 필요하며, 이는 React의 기본 구조를 유지하면서 DOM 접근을 제공하려는 설계 의도이다. 또한, Hook들이 아직까진 ref를 부모-자식 간에 전달하는 문제를 해결하진 못했다.
// 함수형 컴포넌트의 예
function example() {
return (
<div>
<h2>Hello World!</h2>
</div>
);
}
// Class형 컴포넌트의 예
import React, { Component } from 'react';
class example extends Component {
render() {
return <div><h2>Hello World!</h2></div>
}
}
forwardRef를 언제 사용하는가?
첫번째, DOM 접근이 필요한 경우
부모 컴포넌트가 자식 컴포넌트의 특정 DOM 요소에 직접 접근해야 할 때
두번째, 라이브러리나 UI 컴포넌트에서
ref를 전달받아 외부에서 DOM을 제어할 수 있는 API를 제공하려는 경우. 예) 포커스 이동, 스크롤 위치 제어 등
세번쨰, HOC (Higher-Order Component) 사용 시
ref를 부모에서 자식까지 그대로 전달해야 할 때
사용 예제
// 기본 사용 예
import React, { forwardRef } from "react";
const Input = forwardRef((props, ref) => (
<input ref={ref} {...props} />
));
function App() {
const inputRef = React.createRef();
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<Input ref={inputRef} placeholder="Type Someting" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
export default App;
위 코드에서 Input 컴포넌트는 함수형 컴포넌트이지만, ref를 통해 내부의 <input> DOM 요소를 부모 컴포넌트가 직접 제어할 수 있게 한다.
// HOC와 사용
import React, { forwardRef } from "react";
const withLogger = (WrappedComponent) => {
return forwardRef((props, ref) => {
console.log("props", props);
return <WrappedComponent ref={ref} {...props} />;
});
};
const Button = forwardRef((props, ref) => (
<button ref={ref} {...props}>
{props.children}
</button>
));
const LoggedButton = withLogger(Button);
function App() {
const buttonRef = React.createRef();
return (
<LoggedButton ref={buttonRef}>Click Me</LoggerButton>
);
}
export default App;
원리 및 동작 방식
- forwardRef는 고차 함수로, 새로운 컴포넌트를 생성한다.
- 이 컴포넌트는 props와 함께 ref를 인자로 받는다.
- ref는 React에서 제공하는 객체로, DOM 노드 또는 클래스형 컴포넌트를 참조할 수 있다.
- 부모 컴포넌트에서 정의된 ref는 forwardRef를 통해 자식의 특정 DOM 요소 또는 하위 컴포넌트에 전달된다.
- 이 과정에서 React는 부모의 ref를 자식의 DOM에 연결하는 역할을 자동으로 수행한다.
- 부모가 ref를 전달 -> forwardRef가 이를 하위 요소에 전달 -> 하위 요소가 이를 DOM에 연결.
주의사항
forwardRef는 함수형 컴포넌트에서만 사용할 수 있다. 클래스형 컴포넌트는 자체적으로 ref를 받을 수 있다.
일반 props와 달리 DOM 요소 또는 클래스형 컴포넌트를 참조하기 위한 목적으로 사용된다. DOM 요소 외의 객체에는 사용할 수 없다.
forwardRef는 DOM 제어가 필요한 경우에만 사용하는 것이 좋다. 무분별하게 사용할 경우 코드가 복잡해 질 수 있다.
Hook과 forwardRef를 함께 사용하는 경우
useImperativeHandle와 함께 사용
import React, { useRef, forwardRef, useImperativeHandle } from "react";
const ChildComponent = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focusInput: () => {
inputRef.current.focus();
},
}));
return <input ref={inputRef} placeholder="Type here" />;
});
function App() {
const childRef = useRef();
const handleFocus = () => {
childRef.current.focusInput();
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleFocus>Focus Input</button>
</div>
);
}
export default App;
useImperativeHandle은 forwardRef와 함께 사용하여 부모가 자식 컴포넌트의 DOM이나 특정 메서드에 접근할 수 있도록 제어한다.