forwardRef가 사라졌다!

밍글·3일 전
4

FE스터디

목록 보기
8/8

react19를 찾아보다가 흥미로운 사실을 발견했다. 그건 바로 forwardRef가 사라졌다는 것이다. 이전에 이미지를 전달했을 떄 forwardRef로 했던 기억이 있어 react19이전에 했었던 프로젝트를 회고할 겸 forwardRef가 어떤것이었고 react19에서는 어떻게 바뀌었는지 알아보도록 하겠다.

forwardRef?

컴포넌트가 ref를 받아 하위 컴포넌트로 전달하도록 하기 위해 사용됐다. 리액트 19 이전에는 ref를 클래스 컴포넌트와 DOM 요소에만 사용할 수 있도록 제한했기 때문에 함수형 컴포넌트에서는 본질적으로 ref를 받을 수 없었다.

ref prop으로 전달 시 undefined를 반환했고 함수형 컴포넌트에서 ref 사용 불가 경고 발생, TypeError발생 등… ref를 바로 전달하기란 불가능했다.

⛔ 사용법 (이젠 의미가 없어졌지만.. react19이전 버전을 사용할 경우는 필수로 해야한다.)
1. 자식 컴포넌트를 forwardRef로 감싸기
2. 두 번째 매개변수로 ref받기
3. 받은 ref를 원하는 DOM요소에 연결하거나 useImperativeHandle를 이용해서 자식 컴포넌트의 메서드 호출하면 된다.

useImperativeHandle

ref에 직접 내재화된 메서드를 정의할 수 있는 hook이다. 필자는 물론 해당 hook을 사용해 본적이 없다.

하지만 사용을 했을 때 다음과 같은 장점이 있다고 한다.

  1. 자식 컴포넌트의 내부 메서드를 부모에 노출시킬 수 있다.
  2. DOM 요소에 직접 접근하지 않고도 자식 컴포넌트와 상호작용이 가능하다.
  3. 커스텀 메서드나 값들을 ref를 통해 부모에게 제공할 수 있다.

활용 예제코드는 아래와 같다.

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;

typescript이슈

타입스크립트에서 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}
              />

react19에서의 변경사항

리액트 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>
      // 생략... 
  );
};

변경하면서의 장점

  1. 간단한 사용 : 더 이상 ref를 넘기기 위해 forwardRef를 사용해야 하는 복잡성이 줄어들었다.
  2. 보일러플레이트 코드가 줄어듦 : 매번 불필요하게 함수로 감쌀 필요 없이 깔끔한 코드 작성이 가능해졌다.
  3. 일관된 패턴 : 함수형 컴포넌트, 클래스 컴포넌트, 그리고 DOM 요소에서 ref를 동일한 방식으로 처리할 수 있다.

⚠️주의사항

  • 이전 버전과의 호환성 : forwardRef 코드는 계속 동작하지만, 앞으로를 대비해 점진적으로 마이그레이션 하는 것이 좋다.
  • 라이브러리 호환성 : react query때도 마찬가지로 아직 react19가 적용되지 않은 라이브러리에서는 여전히 forwardRef를 사용할 수 있기 때문에 변경 점을 체크해야 한다.
  • 예외케이스 존재 : 클래스 컴포넌트에서는 ref의 동작이 변경되지 않는다. DOM 요소에서는 JSX 요소에서 계속 ref를 직접 사용할 수 있다.

forwardRef는 호환성을 위해 현재는 있지만 점차 사라질 것이므로 점진적으로 마이그레이션을 하는 것이 좋다.

참고자료

forwardRef – React

[번역] 리액트 19 forwardRef 지원 중단: 앞으로 ref를 전달하기 위한 표준 가이드

[React]React 버전 18과 19에서의 ref 전달 방식 비교 분석하기(forwardRef)

[React] forwardRef 사용법, typescript 사용 시 발생하는 에러

profile
예비 초보 개발자의 기록일지

0개의 댓글

관련 채용 정보