본문 바로가기
Frontend/React & Next.js

공식문서로 forwardRef와 useImperativeHandle 알아보기

by 이의찬 2023. 11. 7.

1. forwardRef는?

이제 forwardRef로 들어가보자.

 

forwardRef는 자식 컴포넌트가 부모 컴포넌트에 DOM 노드를 노출할 때 사용한다. 

나 같은 경우는 Atomic 패턴을 이용해 컴포넌트를 나누면서 부모 컴포넌트가 자식 컴포넌트 DOM 노드의 값을 가져가 사용할 일이 많다보니 자연스럽게 사용하게 되었다.

forwardRef를 사용하면 컴포넌트가 ref를 사용하여 부모 컴포넌트에 DOM 노드를 노출할 수 있습니다.
forwardRef는 JSX에서 렌더링할 수 있는 React 컴포넌트를 반환합니다. 일반 함수로 정의된 React 컴포넌트와 달리, forwardRef가 반환하는 컴포넌트는 ref prop을 받을 수도 있습니다.

 

컴포넌트의 DOM 노드는 비공개가 기본이지만, forwardRef()로 컴포넌트를 감싸면 forwardRef()가 JSX로 렌더링 가능한 React 컴포넌트를 반환하는데, 이 컴포넌트는 부모 컴포넌트에서 ref를 props로 전달받아 하위 컴포넌트로 전달할 수 있다. 즉, 부모에 DOM노드를 노출할 수 있게 된다.

 

하지만 당연히 기본적으로 막아놓은데는 이유가 있다. 

컴포넌트 내부의 DOM 노드에 대한 ref를 노출하면 나중에 컴포넌트의 내부를 변경하기가 더 어려워진다는 점에 유의하세요. 일반적으로 버튼이나 텍스트 input과 같이 재사용 가능한 저수준 컴포넌트에서 DOM 노드를 노출하지만, 아바타나 댓글 같은 애플리케이션 레벨의 컴포넌트에서는 노출하고 싶지 않을 것입니다.

 

부모 컴포넌트에 DOM 노드를 노출하고 부모 컴포넌트에서 이를 가져다 사용하는 방식으로 구현한다면 컴포넌트간의 의존성이 생기게 되고, 독립성과 재사용성은 낮아질 수 밖에 없다. 그렇기에 가능하면 버튼이나 text input과 같은 말단 요소가 아니면 사용하지 않는 것이 좋다. 사람은 부모 자식 사이가 끈끈한게 좋지만 컴포넌트 세계에서는 불효자가 되어야 한다는 것을 명심하자.

독립적이고 재사용성 높은 컴포넌트를 만들기 위해서 노력하자


2. 부모 컴포넌트에 DOM 노드 노출하기

바로 예시 코드와 함께 살펴보자.

 

App.js(부모)

export default 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>
  );
}

ref에 useRef를 호출해서 할당한 뒤, MyInput 컴포넌트에 props로 ref를 넘겨주었다. 

 

MyInput.js(자식)

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

export default MyInput;

1. 공식문서에서 말하는 대로 우선 컴포넌트를 forwardRef()로 감싸놓았다.

2. 그리고 props로 받은 ref를 input 태그에 넣어주면

3. 그 태그의 ref.current값에 해당 ref를 props로 전달한 부모 컴포넌트에서 접근할 수 있게 된다.

 

만약 여러 컴포넌트를 거쳐서 ref를 전달하려면 아래와 같이 하면 된다.

<FormField label="Enter your name:" ref={ref} isRequired={true} />

위와 같이 FormField 컴포넌트에 ref={ref}로 props를 넘겨주고

const FormField = forwardRef(function FormField({ label, isRequired }, ref) {
  const [value, setValue] = useState('');
  return (
    <>
      <MyInput
        ref={ref}
        label={label}
        value={value}
        onChange={e => setValue(e.target.value)} 
      />
      {(isRequired && value === '') &&
        <i>Required</i>
      }
    </>
  );
});

export default FormField;

 

FormField 컴포넌트를 forwardRef로 감싸서 App 컴포넌트에 DOM 노드를 공개한 뒤, MyInput 컴포넌트에 마찬가지로 props로 전달받은 ref를 전달해주면 된다.


3. DOM 노드 대신 명령형 핸들 노출하기

전체 DOM 노드를 노출하는 대신 보다 제한된 메서드 집합을 사용하여 명령형 핸들이라고 하는 사용자 정의 객체를 노출할 수 있습니다.

 

전체 DOM 노드를 노출하는 대신 useImperativeHandle Hook을 이용할 수도 있다.  이것도 우선 코드부터 보자.

 

MyInput.js

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} />;
});

export default MyInput;

1. const inputRef = useRef(null)를 통해서 DOM 노드를 가지고 있을 별도의 ref를 선언하고

2. useImperativeHandle Hook에 매개변수로 ref를 전달한 뒤

3. Hook 내부에 노출할 값을 위와 같이 지정해주면 된다.

 

App.js

export default function Form() {
  const ref = useRef(null);

  function handleClick() {
    ref.current.focus();
    // This won't work because the DOM node isn't exposed:
    // 이 작업은 DOM 노드가 노출되지 않으므로 작동하지 않습니다.
    // ref.current.style.opacity = 0.5;
  }

  return (
    <form>
      <MyInput label="Enter your name:" ref={ref} />
      <button type="button" onClick={handleClick}>
        Edit
      </button>
    </form>
  );
}

이제 DOM 노드 전체가 노출되는 대신 { focus, scrollIntoView } 객체만을 받게 된다. 이 부분은 직접 사용해보지 못해서 아직 명확하게 이해가 가지 않아서 좀 더 찾아봐야겠다.

 

"저기요, 그럼 forwardRef의 사용예시는 없나요?"

 

그 예시를 작성하다가 개념을 먼저 설명해야 할 거 같아서 이 글을 썼다..

다음 글은 forwardRef를 이용해서 어떻게 로그인과 회원가입 등 회원관리를 구현했는지 작성할 예정이다.

 

react-hook-form과 atomic 패턴으로 재사용성을 고려한 로그인/회원가입 컴포넌트 만들기

GitHub - Fintech-Moic/Moic: 카드 혜택과 기프티콘 간편 제공 웹앱 프로젝트 카드 혜택과 기프티콘 간편 제공 웹앱 프로젝트. Contribute to Fintech-Moic/Moic development by creating an account on GitHub. github.com 혹시 실

cksxkr5193.tistory.com


4.퍼런스