My Boundary As Much As I Experienced

React) ref를 하위 컴포넌트에 전달하는 두 방법, 'ForwardRef로 전달하기'와 'Props 중 하나로 전달하기'가 유의미하게 차이 있나? 본문

FrontEnd/React

React) ref를 하위 컴포넌트에 전달하는 두 방법, 'ForwardRef로 전달하기'와 'Props 중 하나로 전달하기'가 유의미하게 차이 있나?

Bumang 2024. 8. 22. 23:11

공부하게 된 계기

리액트네이티브로 ref를 쓸 때 아래같은 에러가 나올 때가 있다. 딱 봐도 ForwardRef를 쓰라는 얘기같은데

자세히 보면 함수형 컴포넌트에선 직접 ref를 받을 수 없다는 얘기다. 지금까지 리액트 웹개발하면서

함수형 컴포넌트에서 ref를 props로 잘만 사용해왔는데??? 이상하다. 왜 리액트네이티브에선 이런 에러가 나는가?

Warning: Function components cannot be given refs.
Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

 

일단 그전에 props와 ref를 비교해보자.

 

Props와 Ref의 비교

  • props는 데이터와 이벤트 핸들러를 전달하기 위한 용도: props는 컴포넌트의 데이터 흐름을 담당하며, 컴포넌트의 렌더링 결과를 제어하거나 자식 컴포넌트로 이벤트 핸들러를 전달하는 데 사용된다. props는 주로 컴포넌트 간의 데이터 흐름과 렌더링에 영향을 주는 방식으로 사용되며, ref와는 다른 역할을 한다. 또한 props는 React의 렌더링 사이클과 밀접하게 연결되어 있다. props의 변경은 재렌더링을 유발하고, React의 상태 관리 흐름과도 긴밀하게 연결된다.
  • Ref는 프로그래밍적인 조작만을 위해 특정 요소를 선택하기 위한 것이기 때문에 데이터 흐름과 무관하다. ref는 컴포넌트가 재렌더링되더라도 동일한 객체로 유지되기 때문에, 재렌더링이 발생해도 ref가 가리키는 대상(DOM 요소나 컴포넌트 인스턴스)은 변경되지 않는다. 이로 인해 ref는 렌더링과 무관하게 동일한 객체를 참조할 수 있게 된다.

 

ref로 DOM요소를 구독하는 2가지 방법

1. ref를 props로 전달

ref를 일반 props로 전달하는 방법이다. 이 경우, 전달된 ref는 단순히 부모에서 자식으로 전달된 하나의 prop로 간주되며,

특별하게 처리하지 않는다. 특별한 props로 처리하지 않더라도 ref를 컴포넌트 내부에서 처리하거나 다른 하위 컴포넌트로 전달할 수 있다.

 

2. React.forwardRef를 사용하여 전달

 React에서는 ref를 특별한 prop으로 취급하여 이를 함수형 컴포넌트에서 직접 사용하려면  React.forwardRef를 사용하여 전달할 수 있다. 그리고 이것이 권장된다.

 

왜 ref전달 시 props 중 하나로 처리하기보단 forwardRef를 쓰는게 권장되는가? (과연 성능차이는 존재하는가?)

ref는 원래 컴포넌트 외부에서 특정 DOM 요소나 클래스 컴포넌트의 인스턴스에 직접 접근하기 위한 수단이다.

React는 ref를 통해 컴포넌트 외부에서 내부 요소에 대한 직접적인 조작을 허용하지만

이 과정에서 ref는 React의 재조정(render) 흐름을 깨뜨리지 않고 순수하게 DOM에 접근하는 목적을 유지해야된다.

 

그리고 props와 달리 주로 내부 상태와 라이프사이클과 관련된 작업이 아니기 때문에 

직접적인 조작을 수행할 때 사용되므로 다른 방식으로 처리되어야한다는 의미적으로도 forwardRef를 사용하는게 더 명확하다.

 

'이렇게 둘은 의미적으로나 기능적으로 다르기 때문에 나눠서 받자.' 라는게 forwardRef의 아이디어이다.

그러나 나는 속으로 'props 중 하나로 ref를 처리해도 되던데? 지금까지 나는 리액트에선 그렇게 해왔었는데?' 라고 생각했는데..

맞다. props 중 하나로 취급한다고 기능적으로 달라지는건 없다... 성능적인 차이도 거의 없다는게 정설이다.

 

그냥 의미적으로 구조적으로 더 명확하게 구분하라는 말이다.

그리고 리액트의 컴파일러보다 리액트 네이티브의 컴파일러가 이 부분에 대해 더 엄격한 것 뿐이었다.

 

 

 

+ 마지막으로 forwardRef를 써야할 때와 안 써도 될 때를 구분하기

써야될 때:

ref로 실제 DOM요소를 구독하는 컴포넌트 안에서는 forwardRef로 wrapping해주는 것이 좋다.

import React, { forwardRef, useRef } from 'react';

// 컴포넌트의 props 타입 정의
interface MyInputProps {
  placeholder?: string;
}

// forwardRef를 사용하여 ref와 props를 함께 처리하는 함수형 컴포넌트
const MyInput = forwardRef<HTMLInputElement, MyInputProps>(({ placeholder }, ref) => {
  return <input ref={ref} placeholder={placeholder} />;
});

const ParentComponent: React.FC = () => {
  const inputRef = useRef<HTMLInputElement>(null);

  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus(); // input 요소에 포커스를 설정
    }
  };

  return (
    <div>
      <MyInput ref={inputRef} placeholder="Type here..." />
      <button onClick={focusInput}>Focus the input</button>
    </div>
  );
};

export default ParentComponent;

 

안 써도 될 때:

ref로 실제 참조를 발생시키는 부분이 아니라 그저 전달한 하는 컴포넌트라면 그냥 prop으로 전달하는 것이 좋다.

// 이 컴포넌트는 단순히 데이터를 처리하거나 다른 컴포넌트를 감싸고 있을 뿐, ref를 직접 사용하지 않습니다.
const WrapperComponent = ({ children }: Props) => {
  // 여기는 forwardRef가 필요 없습니다.
  return <div>{children}</div>;
};

const ParentComponent = () => {
  const inputRef = React.useRef<HTMLInputElement>(null);

  return (
    <WrapperComponent>
      <input ref={inputRef} placeholder="Just wrapped input" />
    </WrapperComponent>
  );
};