react19를 찾아보다가 흥미로운 사실을 발견했다. 그건 바로 forwardRef가 사라졌다는 것이다. 이전에 이미지를 전달했을 떄 forwardRef로 했던 기억이 있어 react19이전에 했었던 프로젝트를 회고할 겸 forwardRef가 어떤것이었고 react19에서는 어떻게 바뀌었는지 알아보도록 하겠다.
컴포넌트가 ref를 받아 하위 컴포넌트로 전달하도록 하기 위해 사용됐다. 리액트 19 이전에는 ref를 클래스 컴포넌트와 DOM 요소에만 사용할 수 있도록 제한했기 때문에 함수형 컴포넌트에서는 본질적으로 ref
를 받을 수 없었다.
ref prop으로 전달 시 undefined를 반환했고 함수형 컴포넌트에서 ref 사용 불가 경고 발생, TypeError발생 등… ref를 바로 전달하기란 불가능했다.
⛔ 사용법 (이젠 의미가 없어졌지만.. react19이전 버전을 사용할 경우는 필수로 해야한다.)
1. 자식 컴포넌트를 forwardRef로 감싸기
2. 두 번째 매개변수로 ref받기
3. 받은 ref를 원하는 DOM요소에 연결하거나 useImperativeHandle를 이용해서 자식 컴포넌트의 메서드 호출하면 된다.
ref에 직접 내재화된 메서드를 정의할 수 있는 hook이다. 필자는 물론 해당 hook을 사용해 본적이 없다.
하지만 사용을 했을 때 다음과 같은 장점이 있다고 한다.
활용 예제코드는 아래와 같다.
increment()와 getValue() 메서드를 부모에서 호출하며 부모에서 자식의 내부 상태를 직접 조작하지 않고도 제어 가능하다. 마지막으로 내부 상태를 안전하게 조회하고 수정할 수 있는 인터페이스 제공하는 예시의 코드이다.
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
// 자식 컴포넌트에 노출할 메서드 타입
interface CounterHandle {
increment: () => void;
getValue: () => number;
}
// 최소한의 카운터 컴포넌트
const Counter = forwardRef<CounterHandle>((props, ref) => {
const [count, setCount] = React.useState(0);
// 부모에게 노출할 메서드 정의
useImperativeHandle(ref, () => ({
increment: () => setCount(prev => prev + 1),
getValue: () => count
}));
return <div>내부 카운트: {count}</div>;
});
// 부모 컴포넌트
const App = () => {
const counterRef = useRef<CounterHandle>(null);
const [message, setMessage] = React.useState('');
return (
<div>
<Counter ref={counterRef} />
<button onClick={() => counterRef.current?.increment()}>
외부에서 증가
</button>
<button
onClick={() => {
const value = counterRef.current?.getValue();
setMessage(`현재 값: ${value}`);
}}
>
값 확인
</button>
{message && <p>{message}</p>}
</div>
);
};
export default App;
타입스크립트에서 forwardRef 사용 시 'Component definition is missing display name' 에러가 발생했었다. 이는 forwardRef()
함수를 호출할 때 익명 함수를 넘기게 되면 브라우저에서 React 개발자 도구를 사용할 때 컴포넌트의 이름이 나오지 않아 발생하는 이슈였었다.
이를 해결하기 위해서는 CustomInput.displayName = "CustomInput";
와 같이 함수 호출의 결과를 displayName에 설정하거나, CustomInput = forwardRef(CustomInput);
와 같이 forwardRef() 함수의 호출 결과로 기존 컴포넌트를 대체하는 방법이 있다. 필자는 전자의 방법을 활용하였다. 이것들을 하지 않으면export가 제대로 되지 않았었다.
필자는 Next.js Image컴포넌트를 활용하여 이미지src를 전달할 때 사용하였다. Next.js의 Image
컴포넌트는 최적화된 이미지 처리를 위한 특별한 컴포넌트이며, 내부적으로 복잡한 최적화를 수행하며, 이 과정에서 ref를 올바르게 전달하려면 forwardRef
가 필요했다. ref를 지정해서 하면 다른 컴포를 건드리지 않고도 ref를 전달받을 수 있었다.
const GuestImage = (prop : prop) => {
return (
<ImageBox>
<Image src={prop.src} alt='이미지 경로' fill className='rounded-[20px]'/>
</ImageBox>
)
}
interface ILayout {
imgsrc: string;
title: string;
first: string;
now: string;
}
const GuestResultLayout = forwardRef<HTMLDivElement, ILayout>((props, ref) => {
return (
<div ref={ref} className="py-2">
<div className="mb-8 flex justify-center">
<GuestImage src={props.imgsrc} />
</div>
);
});
// ✅ display name 설정
GuestResultLayout.displayName = "GuestResultLayout";
export default GuestResultLayout;
// 부모컴포넌트에서 해당 GuestResultLayout을 사용할 때
<GuestResultLayout
imgsrc={imgUrl} // 함수 컴포넌트는 ref가 존재하지 않음!
title={data.data.title}
first={data.data.first}
now={data.data.now}
/>
리액트 19에서는 다른 프로퍼티처럼 함수형 컴포넌트에 ref
를 직접 전달할 수 있게 되어서 더 이상 forwardRef를 사용할 필요가 없어졌다.
react19이전과 이후의 코드차이는 결론만 말하자면 forwardRef가 사라지고 ref타입을 지정해줘야 한다. react19버전이었다면 코드는 ****다음과 같았을 것이다.
type prop = {
src: string;
ref?: React.Ref<HTMLImageElement>;
};
const GuestImage = (prop : prop) => {
// ref prop을 직접 받음 (React 19)
const { src, ref } = prop;
return (
<ImageBox>
<Image
ref={ref}
src={src}
alt='이미지 경로'
fill
className='rounded-[20px]'
/>
</ImageBox>
);
};
// ref 프로퍼티의 타입을 추가
const GuestResultLayout = (props: ILayout & { ref?: React.Ref<HTMLDivElement> }) => {
const { imgsrc, title, first, now, ref } = props;
return (
<div ref={ref} className="py-2">
<div className="mb-8 flex justify-center">
<GuestImage src={imgsrc} />
</div>
// 생략...
);
};
forwardRef
를 사용해야 하는 복잡성이 줄어들었다.⚠️주의사항
forwardRef
코드는 계속 동작하지만, 앞으로를 대비해 점진적으로 마이그레이션 하는 것이 좋다.forwardRef
를 사용할 수 있기 때문에 변경 점을 체크해야 한다.forwardRef는 호환성을 위해 현재는 있지만 점차 사라질 것이므로 점진적으로 마이그레이션을 하는 것이 좋다.
[번역] 리액트 19 forwardRef 지원 중단: 앞으로 ref를 전달하기 위한 표준 가이드