forwardRef

박정호·2022년 9월 22일
0

React.js

목록 보기
5/6
post-thumbnail

⭐️ ForwardRef

React.forwardRef는 부모 컴포넌트에서 자식 컴포넌트로 자동으로 ref를 전달하여 부모가 자식 ref를 참조하는 기술

🧐 사용 이유

✏️ DOM 엘리먼트로 ref 전달하기
함수형 컴포넌트는 인스턴스가 없기 때문에 함수형 컴포넌트를 React.forwardRef로 감싸주면 ref사용이 가능하다.

쉽게 말해, ref는 prop으로 전달하지 못하기 때문에, forwardRef을 사용하여 함수형컴포넌트 두번째 인자로 ref prop으로 전달이 가능한 것이다.

✏️ 고차 컴포넌트(HOC) 로 ref 전달하기

💡 잠깐) HOC(고차컴포넌트)?

Go : HOC(고차컴포넌트)

✏️ 개발자도구(DevTools)에 사용자 지정 이름 표시하는 방법
react 앱을 개발하다보면 dev tools를 사용하여 화면단에 어떤 컴포넌트들이 존재하는지 확인이 가능하다. 하지만, forwardRef를 이용하면서 익명 함수로 넘겨주면 dev tools에는 forwardRef라고 표시된다. 따라서, 다음과 같이 하여 컴포넌트명을 표시할 수 있다.

1. 함수 선언문
렌더링 함수를 지정하면 DevTools에 해당 이름도 포함된다.(예,”ForwardRef(myFunction)“)

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

2. displayName 이용

function logProps(Component) {
  class LogProps extends React.Component {
    // ...
  }

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

  // DevTools에서 이 컴포넌트에 조금 더 유용한 표시 이름을 지정하세요.
  // 예, "ForwardRef(logProps(MyComponent))"
  const name = Component.displayName || Component.name;
  forwardRef.displayName = `logProps(${name})`;

  return React.forwardRef(forwardRef);
}
const FancyButton = React.forwardRef((props, ref) => (
  <button ref={ref}>
    {props.children}
  </button>
));

FancyButton.displayName = 'FancyButton';

⚙️ 작동 방법

1️⃣ 작동 1.

✏️ 다음과 같은 경우 Children컴포넌트의 button태그에 접근하고 싶다면?

function Parent() {

    return (
        <div>
            <Children />
        </div>
    )
}

function Children() {
 const hnadleFunc = () =>{
  console.log("hello world")
 }
    return (
        <buntton>
            호로롱
        </buntton>
    )
}

✏️ 1. 부모컴포넌트에서 ref 생성

function parent() {
 const childRef = useRef
    return (
        <div>
            <Children ref={childRef} />
        </div>
    )
}

✏️ 2. 자식컴포넌트를 React.forwardRef()안에 작성

  • 자식컴포넌트의 첫번째 파라미터는 props를, 두번째는 ref를 받는다.
  • 전달된 ref를 자식컴포넌트 button태그에 연결
const Children = React.forwardRef((props,ref)=>{
const hnadleFunc = () =>{
  console.log("hello world")
 }
    return (
        <buntton ref={ref}>
            호로롱
        </buntton>
    )
})

2️⃣ 작동 2.

✏️ 다음과 같은 코드는 ref를 직접 제어하여서 Enter시에 자동으로 포커스가 이동하는 코드이다.

<App.js>
function App() {
  const nameRef = useRef(null);
  const submitRef = useRef(null);

  useEffect(() => { // 컴포넌트 업데이트 시 입력창에 자동으로 커서 포커싱
    alert("페이지 로딩됨");
    nameRef.current.focus();
  }, []);

  const onKeyDown = (e) => { // Enter 시에 제출되는 포커싱
    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;

✏️ 위와 같이 input태그를 작성하기도 하지만, 코드의 간결성과 재사용성을 위하여 하위 컴포넌트를 이용하여 로직을 작성한다. (CustomInput이라는 하위 컴포넌트 생성)

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

✏️ App.js의 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>
  );
}

✏️ type, onKeyDown, placeholder의 경우에는 props로 전달되는 것을 알 수 있다. 하지만, useRef의 경우에는 App.js에만 존재하는 것인데 하위컴포넌트인 CustomInput에서도 직접제어가 가능할까?

👉 함수 컴포넌트에는 ref가 존재하지 않기 때문에 에러가 발생한다.

✏️ 따라서, 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;

3️⃣ 작동 3.

✏️ 부모 컴퍼넌트에서 자식 컴퍼넌트 안 함수 작동하기

  1. 부모에서 사용할 함수를 useImperativeHandle()함수 안에서 선언
  2. useImperativeHandle의 첫번째 파라미터는 ref를 넣어주고 두번째는 콜백함수
  3. 두번째 파라미터 함수 안에서 부모에서 실행시킬 함수 선언
  4. 부모에서 함수 작동
function parent() {
 const childRef = useRef
    return (
        <div>
            <Children ref={childRef} />
        <button onClick ={()=>{ref.current.handleFunc()}} //1번, 4번
        </div>
    )
}
const Children = React.forwardRef((props,ref)=>{

  useImperativeHandle(ref, () => ({ //2번
      hnadleFunc() { // 3번
  console.log("hello world")
      },
    }));
    return (
        <buntton ref={ref}>
            호로롱
        </buntton>
    )
})

🧨 주의사항

forwardRef를 통해 자식컴포넌트의 DOM요소에 접근한다는 것은 자식 컴포넌트가 가진 DOM노드를 외부로 노출시키는 거기 때문에 자식컴포넌트의 캡슐화에 대한 장점을 없애는 것과 같다.

따라서, 엘리먼트에 포커스를 주거나 애니매이션을 관리하거나 DOM노드에 접근이 불가피한 경우에 사용하도록 하자!

출처 및 참고하면 좋은 사이트

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글