관리 메뉴

즐겁게, 코드

forwardRef로 함수 컴포넌트의 ref 전달하기 본문

🎨 프론트엔드/React.js

forwardRef로 함수 컴포넌트의 ref 전달하기

Chamming2 2021. 4. 28. 20:48

토스의 첫 개발 컨퍼런스, SLASH21이 막을 올렸습니다!

저는 이런 컨퍼런스 홍보 페이지를 보면 항상 개발자 도구에 먼저 손이 가곤 하는데, 이번에는 특이하게도 페이지가 리액트로 제작되어 있어 컴포넌트를 구경하던 중 처음 보는 forwardRef 성분에 대해 궁금증을 갖게 되었습니다. 🤔

🛠 HTML 노드를 커스텀 컴포넌트로 교체하기

엔터를 누르면 버튼으로 포커스가 이동하고, 한번 더 엔터를 누르면 얼럿이 출력됩니다.

import { useEffect, useRef } from "react";
import Input from "./components/Input";

function App() {
  const nameRef = useRef(null);
  const submitRef = useRef(null);

  useEffect(() => {
    alert("페이지 로딩됨");
    nameRef.current.focus();
  }, []);

  const onKeyDown = (e) => {
    if (e.key === "Enter") {
      submitRef.current.focus();
    }
  };

  const submitKeyDown = () => {
    alert("form submitted");
  };

  return (
    <div className="App">
      <header className="App-header">
        <input
          type="text"
          ref={nameRef}
          onKeyDown={onKeyDown}
          placeholder="이름을 입력하세요"
        ></input>
        <button ref={submitRef} onKeyDown={submitKeyDown}>
          제출
        </button>
      </header>
    </div>
  );
}

export default App;

useRef 로 ref를 직접 제어해 엔터를 누르면 다음 요소로 포커스가 이동하도록 한 예시입니다.

그런데, 실제로 리액트 컴포넌트를 작성할 때는 이렇게 input 태그를 사용하기보다는 커스텀한 입력 컴포넌트를 사용하는 경우가 많습니다.

// 커스텀한 입력 컴포넌트
const CustomInput = ({ type, onKeyDown, placeholder }) => {
  return (
    <input
      type={type}
      onKeyDown={onKeyDown}
      placeholder={placeholder}
    ></CustomInput>
  );
};
// input 태그를 CustomInput 컴포넌트로 대체
return (
    <div className="App">
      <header className="App-header">
        <CustomInput
          type="text"
          ref={nameRef}
          onKeyDown={onKeyDown}
          placeholder="이름을 입력하세요"
        ></CustomInput>
        <button ref={submitRef} onKeyDown={submitKeyDown}>
          제출
        </button>
      </header>
    </div>
  );
}

이제 입력을 받는 input 태그를 CustomInput 이라는 컴포넌트로 교체했는데요, 과연 CustomInput 컴포넌트도 useRef 와 ref를 통한 직접 제어가 가능할까요? 

아! 안되네요!

ref를 통해 CustomInput 컴포넌트를 조작하려 하면 null 값을 참조하려 한다는 오류가 출력되는데, 과연 원인이 무엇일까요?

🖋 원인 - 함수 컴포넌트는 ref가 존재하지 않는다!

출처 - 리액트 공식 문서(https://ko.reactjs.org/docs/refs-and-the-dom.html) 중

바로 함수 컴포넌트의 ref는 애초에 존재하지 않기 때문입니다.

그럼 ref를 통해 함수 컴포넌트를 직접 제어하는건 완전히 불가능한 일인 걸까요?

React.forwardRef

바로 이럴 때 forwardRef를 활용할 수 있습니다.

forwardRef를 사용하면 부모 컴포넌트로부터 하위 컴포넌트로 ref를 전달할 수 있는데요, 이렇게 전달받은 ref를 HTML 요소의 속성으로 넘겨줌으로써 함수 컴포넌트 역시 ref를 통한 제어가 가능해집니다.

// props 목록 뒤에 ref를 별도로 전달받는 모습입니다.
const CustomInput = React.forwardRef(({ type, onKeyDown, placeholder }, ref) => {
  return (
    <input
      type={type}
      onKeyDown={onKeyDown}
      placeholder={placeholder}
      // 전달받은 ref는 HTML 속성으로 전달됩니다.
      ref={ref}
    ></input>
  );
});

export default CustomInput;

또는 이렇게 사용할 수도 있습니다.

const CustomInput = ({ type, onKeyDown, placeholder }, ref) => {
  return (
    <input
      type={type}
      onKeyDown={onKeyDown}
      placeholder={placeholder}
      ref={ref}
    ></input>
  );
};

// 위의 방법과 동작상 차이는 없습니다.
const forwardedRefInput = React.forwardRef(CustomInput);
export default forwardedRefInput;

TL;DR

  • 커스텀 컴포넌트는 ref 속성이 존재하지 않아 아래와 같이 ref를 전달받을 수 없습니다.
// 커스텀한 입력 컴포넌트
<CustomInput
  type="text"
  ref={nameRef} // 함수 컴포넌트는 ref가 존재하지 않음!
></CustomInput>

따라서 컴포넌트가 ref를 전달받기 위해서는 React.forwardRef 로 컴포넌트를 감싸준 후, props 뒤에 별도로 전달된 ref를 활용합니다.

// forwardRef로 컴포넌트를 감싼 모습
const CustomInput = React.forwardRef({ type }, ref) => {
  return (
    <input
      type={type}
      // 전달받은 ref는 HTML 속성으로 전달됩니다.
      ref={ref}
    ></input>
  );
});

export default CustomInput;

 

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆