forwardRef

dana·2022년 12월 1일
1

React.js

목록 보기
19/20
post-thumbnail

요약
Ref를 자식 요소에게 전달하고 싶을 때 사용하는 기법

사용 방법

일반적으로는 부모의 ref를 사용하는 것은 컴포넌트간 의존성을 생성하기 때문에 잘 사용되지 않고, 주로 버튼, 인풋 등과 같은 다른 컴포넌트를 사용하지 않는 말단 요소에서 많이 사용된다.

부모 요소의 값을 자식에서 전달하는 방식으로 흔히 쓰이는 props를 이용해 전달하면 되지 않을까? 생각할 수도 있지만, refkey와 마찬가지로 react에서 다르게 처리되어 props로 전달되지 않기 때문에 forwardRef 사용이 필수적이다.

그래서 코드를 다음과 같이 ref를 props로 넘겨주는 방식으로 작성하면

App.js

function App() {
  const textValue = useRef();

  console.log(textValue.current);

  return (
    <div className="App">
      <RefInput ref={textValue} />
      <div>{textValue.current?.value}</div>
    </div>
  );
}

export default App;

RefInput.jsx

export const RefInput = ({ ref }) => {
  return <input ref={ref} type={"text"} />;
};


다음과 같이 forwardRef를 쓰라고 경고를 보낸다.

근데 props이름을 ref가 아닌 다른 이름으로 ref를 내려주는 경우엔 에러 없이 잘 내려가긴 한다.. 🤨

// App.js
<RefInput passref={textValue} />
// RefInput.jsx
export const RefInput = ({ passref }) => {
  return <input ref={passref} type={"text"} />;
};

올바른 방법으로 사용하기 위해선 다음과 같이 forwardRef를 적용해 내려주어야 한다.

RefInput.jsx

import { forwardRef } from "react";

export const RefInput = forwardRef((props, ref) => {
  return <input ref={ref} type={"text"} />;
});

고차 컴포넌트에서의 forwardRef

고차 컴포넌트는 모든 props를 래핑된 컴포넌트에 전달하는 것이 원칙이지만, props에 ref는 전달되지 않기 때문에 원하는 대로 작동하지 않는다.

// 공식문서에 나와있는 예시
function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }

    render() {
      const {forwardedRef, ...rest} = this.props;

      return <Component ref={forwardedRef} {...rest} />;
    }
  }

  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

Hook의 등장으로 HOC의 필요성이 낮아졌다. 고럼 훅에서는 ref 전달이 어떤식으로 진행되려나..? 하고 찾아보니 createRef가 useRef로 변화하고 부모의 ref를 전달받는 방식으로 forwardref를 사용하는데는 변화가 없었다.

app.jsx

import { ChildModal } from "./ChildModal";
import { useParent } from "./useParent";

function App() {
  const { modal, modalRef, toggleModal, handleOutsideClick } = useParent();
  return (
    <div className="page" onClick={() => handleOutsideClick()}>
      <button type="button" onClick={() => toggleModal()}>
        Open modal
      </button>
      {modal && <ChildModal ref={modalRef} toggleModal={toggleModal} />}
    </div>
  );
}

export default App;

useParent.js

import { useRef, useState } from "react";

export const useParent = () => {
  const [modal, setModal] = useState(false);
  const modalRef = useRef(null);

  const toggleModal = () => {
    setModal(!modal);
  };

  const handleOutsideClick = (e) => {
    if (modalRef.current && !modalRef.current.contains(e?.target)) {
      setModal(false);
    }
  };

  return { modal, modalRef, toggleModal, handleOutsideClick };
};

ChildModal.jsx (forwardref 사용)

import { forwardRef } from "react";

export const ChildModal = forwardRef((props, ref) => {
  const { toggleModal } = props;

  return (
    <div className="modal" ref={ref}>
      <button type="button" onClick={toggleModal}>
        Close modal
      </button>
    </div>
  );
});

사용시 주의사항

1. 조건부 사용 금지

forwardRef로 이뤄진 컴포넌트가 존재할 때만 적용하는 경우, 라이브러리 동작 방식을 변경하거나 리액트를 업데이트할 때 문제가 발생할 수 있다.

2. 리액트 개발자 도구 사용시 컴포넌트 이름 표기


리액트 개발자도구에서 해당 컴포넌트를 확인하면 forwardRef로 표기되는 것을 확인할 수 있다. 이름이 없이 보이는 이유는 forwardRef 사용시 익명함수를 이용하기 때문인데, 이를 명확하게 알아보기 위해 다음과 같은 방법으로 이름을 지정해 줄 수 있다.

익명함수 대신 이름을 지정한 함수 사용하기

export const ChildModal = forwardRef(function modalName(props, ref) {
  const { toggleModal } = props;

  return (
    <div className="modal" ref={ref}>
      <p>모달 내부~~</p>
      <button type="button" onClick={toggleModal}>
        Close modal
      </button>
    </div>
  );
});

displayName 속성 이용하기

export const ChildModal = forwardRef((props, ref) => {
// 기존 내용과 동일
});

ChildModal.displayName = "useDisplayName";

말단 컴포넌트에서만 사용하기

해당 속성은 부모의 DOM 노드를 가져와 사용하는 것이기 때문에 중첩해서 사용시 컴포넌트간 결합도가 높아지게 된다. 따라서 가장 말단 컴포넌트에 적용해 컴포넌트간의 결합도를 최소화하여 사용하는 것이 좋다.

참고
https://www.daleseo.com/react-forward-ref/
https://reactjs.org/docs/forwarding-refs.html#gatsby-focus-wrapper

profile
PRE-FE에서 PRO-FE로🚀🪐!

0개의 댓글