forwardRef

Chaerin Kim·2023년 11월 27일

forwardRef를 사용하면 컴포넌트가 ref를 사용하여 부모 컴포넌트에 DOM 노드를 노출할 수 있음.

const SomeComponent = forwardRef(render)

Reference

forwardRef(render)

컴포넌트가 ref를 받아서 하위 컴포넌트로 전달하도록 하려면 forwardRef()를 호출:

import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  
  // ...
});

Parameters

  • render: 컴포넌트의 렌더링 함수. React는 컴포넌트가 부모로부터 받은 props와 ref를 가지고 이 함수를 호출함. 반환하는 JSX는 컴포넌트의 출력이 됨.

Returns

forwardRef는 JSX에서 렌더링할 수 있는 React 컴포넌트를 반환함. 일반 함수로 정의된 React 컴포넌트와 달리, forwardRef가 반환하는 컴포넌트는 ref 프로퍼티를 받을 수 있음.

Caveats

  • Strict Mode에서 React는 실수로 발생한 불순물을 찾기 위해 렌더링 함수를 두 번 호출함. 이는 개발 단계 전용 동작이며 프로덕션에는 영향을 미치지 않음. 렌더링 함수가 순수하다면(그래야 함) 컴포넌트의 로직에 영향을 미치지 않을 것. 호출 중 하나의 결과는 무시됨.

render function

forwardRef는 렌더 함수를 인자로 받음. React는 이 함수를 props와 ref를 이용해서 호출함:

const MyInput = forwardRef(function MyInput(props, ref) {
  return (
    <label>
      {props.label}
      <input ref={ref} />
    </label>
  );
});

Parameters

  • props: 부모 컴포넌트가 전달한 props.

  • ref: 부모 컴포넌트가 전달한 ref 어트리뷰트. ref는 객체나 함수일 수 있음. 부모 컴포넌트가 ref를 전달하지 않은 경우 null이 됨. 받은 ref를 다른 컴포넌트에 전달하거나 useImperativeHandle에 전달해야 함.

Returns

JSX에서 렌더링할 수 있는 React 컴포넌트를 반환함. 일반 함수로 정의된 React 컴포넌트와 달리, forwardRef가 반환하는 컴포넌트는 ref prop을 받을 수 있음.


Usage

Exposing a DOM node to the parent component

기본적으로 각 컴포넌트의 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 컴포넌트는 MyInputref를 전달함. MyInput 컴포넌트는 해당 ref를 <input> 브라우저 태그에 전달함. 그 결과 Form 컴포넌트는 해당 <input> DOM 노드에 액세스하여 이 노드에서 focus()를 호출할 수 있음.

컴포넌트 내부의 DOM 노드에 ref를 노출하면 나중에 컴포넌트의 내부를 변경하기가 더 어려워진다는 점에 유의할 것. 일반적으로 버튼이나 텍스트 입력과 같이 재사용 가능한 로우 레벨 컴포넌트에서 DOM 노드를 노출하지만 아바타나 코멘트와 같은 애플리케이션 레벨 컴포넌트에서는 노출하지 않음.

Forwarding a ref through multiple components

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 노드에 액세스하는 방법.

Exposing an imperative handle instead of a DOM node

전체 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 노드에 대해 노출되는 정보를 최소한으로 제한할 수 있음.

참고: 명령형(imperative) 핸들 사용

Pitfall

Ref를 과도하게 사용하지 말 것. 노드로 스크롤하기, 노드에 포커스 맞추기, 애니메이션 트리거하기, 텍스트 선택하기 등과 같이 props로 표현할 수 없는 명령형 동작에만 ref를 사용해야함.

Props로 표현할 수 있는 것이 있다면 ref를 사용해서는 안됨. 예를 들어, 모달 컴포넌트에서 { open, close }와 같은 명령형 핸들을 노출하는 대신, <Modal isOpen={isOpen} />처럼 isOpen을 prop으로 받는 것이 더 좋음. Effect는 props를 통해 명령형 동작을 노출하는 데 도움이 될 수 있음.

0개의 댓글