몽환화

[React] useImperativeHandle 물고뜯기 본문

Front/React

[React] useImperativeHandle 물고뜯기

hyeii 2024. 5. 6. 13:46

useImperativeHandle

컴포넌트의 최상위 레벨에서 useImperativeHandle을 호출해 노출할 ref핸들을 사용자가 직접 정의할 수 있다 

 

import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
      // ... 메서드는 여기에 작성합니다 ...
    };
  }, []);
  // ...
useImperativeHandle(ref, createHandle, dependencies?)

 

ref : forwardRef 렌더 함수에서 두번째 인자로 받은 ref

createHandle : 인자가 없으며 노출하려는 ref 핸들을 반환하는 함수. 해당 ref 함수는 어떠한 유형이든 될 수 있다. 일반적으로 노출하려는 메서드가 있는 객체를 반환한다.

 

그러니까 기본적으로 각 컴포넌트의 DOM 노드는 비공개인데, 포커싱을 허용하기 위해 부모에 DOM 노드를 노출하는 것이 유용할 수 있어 그래서 forwardRef()로 감싸면 돼

여기서 forwardRef에 대해 알아야 한다

forwardRef : ref를 상위 컴포넌트에서 하위 컴포넌트로 전달하고 싶다면 (상위 컴포넌트에서는 접근하고 싶은 ref가 있지만 이를 직접 props로 넣어 사용할 수 없을 때) 사용한다

사실 다른 props로 전달하려면 전달할 수는 있는데 ref를 전달하는 것에 있어서 일관성을 제공하기 위해 만들어졌다!

 

 

부모 컴포넌트는 다음과 같을고얌

// 부모 컴포넌트
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>
  );
}

부모 컴포넌트에서 useRef를 사용해 ref를 만들었다. 이걸 자식 컴포넌트인 MyInput에 전달하고자 한다.

 

// 자식 컴포넌트
import { forwardRef } from "react";

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

자식 컴포넌트인 MyInput을 forwardRef로 감싸서 두번째 인자인 ref를 얘를 노출하고자 하는 DOM 노드에 전달해

이제 위에서 작성한 MyInput 컴포넌트에 ref를 전달할 수 있게 되는것

 

이제 ref를 전달받는것까지는 성공! 얘를 사용자 입맛대로 사용하려면 이제 useImperativeHandle을 호출하면 된다!

import { forwardRef, useImperativeHandle } from "react";

const MyInput = forwardRef(function MyInput(props, ref) {
  useImperativeHandle(
    ref,
    () => {
      return {
        // ... your methods ...
        // ... 메서드를 여기에 입력하세요 ...
      };
    },
    []
  );

  return <input {...props} />;
});

짜잔

근데 여기서 input에 대한 ref는 더이상 전달되지 않는다 

(위의 코드와 비교해보라. ref가 없다)

 

왜 ? 

예를 들어서 전체 input DOM 노드를 노출하지 않고 focus와 scrollIntoView의 두 메서드만을 노출하고 싶다고 가정했을 때, 이를 위해서 실제 브라우저 DOM을 별도의 ref에 유지해야 한다. 그리고 useImperativeHandle을 사용해서 부모 컴포넌트에서 호출할 메서드만 있는 핸들을 노출한다

 

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를 가져오면 focus랑 scrollIntoVIew를 호출할 수 있는거고, input DOM 자체에 대한 액세스 권한은 없는거다

부모한테서 ref를 넘겨받아서 원하는 대로 수정할 수 있게 하는게 useImperativeHandle

이 ref에 원하는 값이나 액션을 정의할 수 있게 되는 것

 

그렇지만 ref의 과도한 사용은 자제하는게 좋다

리액트는 선언형 프로그래밍 방식인데 ref를 쓰는건 명령형 프로그래밍 방식이라 적합하지는 않다 

 

 

참고자료

https://blog.leaphop.co.kr/blogs/35/React_ref%EC%99%80_forwardRef_%EA%B7%B8%EB%A6%AC%EA%B3%A0_useImperativeHandle_%EC%A0%9C%EB%8C%80%EB%A1%9C_%EC%95%8C%EA%B8%B0

https://react-ko.dev/reference/react/useImperativeHandle

'Front > React' 카테고리의 다른 글

[React] Recoil 뜯어보기  (0) 2024.05.22
[React] useLayoutEffect, useDebugValue  (0) 2024.05.06
[React] useReducer 물고뜯기  (0) 2024.05.03
[React] useContext 물고뜯기  (0) 2024.05.02
[React] useRef 물고뜯기  (1) 2024.05.01