React ref, forwardRef, useImperativeHandler 이해하기

Taemin Jang·2024년 2월 1일
0

React ref, forwardRef, useImperativeHandler 이해하기

React로 개발하다보면 DOM 객체에 접근해야할 때 ref를 사용한다.

클래스형 컴포넌트에서 React.createRef()를 사용해서 접근했다면, 함수형 컴포넌트에서는 useRef() 훅을 통해 ref 설정을 할 수 있다.

ref 설정

함수형 컴포넌트 기준, ref는 useRef()를 사용하여 선언하고 초기화한다.

useRef()통해 생성하면 내부에 current라는 변수를 가지고 있는 객체로 생성이 되며, ref에 할당되는 값은 current 변수에 저장이되므로 항상 참조할 때 current 값의 존재 여부를 확인하고 사용해야한다.

const ref = useRef(0);
// { 
//   current: 0
// }

여기서 중요한 점은 useRef()는 객체를 반환한다는 점이다.

자바스크립트에서 전역변수와 참조타입의 변수는 heap영역에 저장되고, 사용되지 않는 변수는 가비지 컬렉터를 이용해 삭제된다.

즉, 매번 렌더링할 때 동일한 객체(동일한 참조값)을 제공하여 값이 변경된다하더라도 같은 메모리 주소를 갖고 있기 때문에 자바스크립트의 동등 비교 연산이 항상 true를 반환한다.

따라서 변경 사항을 감지할 수 없고, 리렌더링이 되지 않는다.

ref로 값 참조

화면이 렌더링되기 위해서는 두 단계로 나뉘어진다.
1. render phase : 렌더링 하는 동안 React는 컴포넌트를 호출하여 화면에 변경된 내용을 파악한다.
2. commit phase : 커밋하는 동안 React는 DOM에 변경 사항을 적용한다.

render phase에서는 DOM 노드가 아직 생성되지 않았으므로 ref.current의 값은 null이 된다.

commit phase에서는 ref.current를 설정한다. 즉, React는 DOM이 업데이트 되기 전에는 ref.current = null로 설정했다가, DOM이 업데이트된 직후 ref.current 값을 DOM노드로 설정한다.

따라서 일반적으로는 이벤트 핸들러에서 ref를 사용하며, ref로 특정 작업을 수행할 이벤트가 없다면 useEffect 내부에서 사용할 수 있다.

forwardRef

함수 컴포넌트에서 커스텀 컴포넌트에 ref를 props로 전달하려고 하면 기본적으로 null이 반환된다. 자식 컴포넌트 DOM에 접근 할 수 없다.

import { useRef } from 'react';

function MyInput(props) {
  return <input {...props} />;
}

export default function MyForm() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <MyInput ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
}

이유는 React에서 기본적으로 컴포넌트가 다른 컴포넌트의 DOM에 접근하는 것을 허용하지 않기 때문이다. 심지어 자신의 자식 컴포넌트도 포함이다.

자식 컴포넌트에 접근하기 위해 forwardRef를 사용하면 외부에서 전달 받은 ref를 컴포넌트 내부에 전달할 수 있다.

사용법은 다음과 같다.

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

...

useImperativeHandler

이전 기업 프로젝트로 돌림판 돌리기를 구현할 때 사용한 경험이 있다.

부모 컴포넌트에서 돌림판 돌리기 버튼을 클릭했을 때 자식 컴포넌트인 돌림판을 돌아가는 함수를 동작시켜야하는 상황이었다.

useImperativeHandler()은 리액트 훅 중 하나로, 부모 컴포넌트에게 노출할 ref 핸들러를 사용자가 직접 정의(ref로 노출 시키는 노드의 일부 메서드만 노출)할 수 있게 해주는 훅이다.

따라서 자식 컴포넌트에 정의한 핸들러를 부모 컴포넌트에 노출시켜 사용할 수 있다.

import {
  forwardRef, 
  useRef, 
  useImperativeHandle
} from 'react';

const Roulette = React.forwardRef((props, ref) => {
  const { items } = props;
	const refs = useRef();

  ...

  // 핸들러를 부모 컴포넌트에 노출시킵니다.
  useImperativeHandle(ref, () => ({
    handleRotateRoulette,
  }));

  const handleRotateRoulette = () => {
		rotation.value = withTiming(rotationAngle, {
      duration: 5000,
      easing: Easing.inOut(Easing.circle),
    });
  };

  ...


// components/Roulette/form  부모 컴포넌트
const RouletteForm = ({ navigation, route }) => {
  const rouletteRef = useRef();

	...

  const handleSubmitVote = () => {
    if (isVote) setVoteText('원판 돌리기');
    else {
      rouletteRef.current.handleRotateRoulette();
    }
  };

  ...

  return (
    ...
    <Roulette
      ref={rouletteRef}
      ...
    />
    ...
  );
};

참고

profile
하루하루 공부한 내용 기록하기

0개의 댓글