thisyujeong.dev

React DOM 노드 측정
July 10, 2023

문제 발생

리액트 프로젝트 중 바텀시트를 구현하면서 컴포넌트가 마운트된 후 DOM의 높이 값을 받아와야 했다.

먼저 내가 구현했던 방식은 다음과 같다. useRef 훅을 사용해 ref 를 지정했고, useEffect 를 이용하여 첫 렌더링 후에 높이 값을 sheetHeight 의 상태값으로 저장하도록 했다.

문제 코드

const BottomSheet = () => {
  const [sheetHeight, setSheetHeight] = useState < number > 0;
  const sheetRef = useRef < HTMLDivElement > null;
 
  useEffect(() => {
    if (sheetRef.current) {
      setSheetHeight(sheetRef.current.clientHeight);
    }
  }, []);
 
  return (
    <div className="bottom_sheet" ref={sheetRef}>
      {/* ...code */}
    </div>
  );
};
 
export default BottomSheet;

useRef가 null로 나오는 이유는 라이프 사이클 특성상 발생하는 문제로 ref는 컴포넌트 리렌더링을 발생시키지 않는다. 따라서 이 문제의 코드는 첫 렌더링 시 null로 초기화되며 이후의 값 또한 렌더링을 발생시키지 않기 때문에 ref는 여전히 null 값을 유지하게 된다.

문제 해결

이를 해결하기 위해서는 리렌더링을 발생시켜 ref 값의 변경을 알려야한다. 이 문제는 리액트 공식문서에서 친절하게 해결법을 알려주고 있다.

One rudimentary way to measure the position or size of a DOM node is to use a callback ref. React will call that callback whenever the ref gets attached to a different node.


DOM 노드의 위치나 크기를 측정하는 기본적인 방법 중 하나는 callback ref를 사용하는 것입니다. 리액트는 ref가 다른 노드에 연결될 때마다 해당 콜백을 호출합니다.

해결 코드

const BottomSheet = () => {
  const [sheetHeight, setSheetHeight] = useState < number > 0;
 
  const sheetRef = useCallback((node) => {
    if (node !== null) {
      setSheetHeight(node.getBoundingClientRect().height);
    }
  }, []);
 
  return (
    <div className="bottom_sheet" ref={sheetRef}>
      {/* ...code */}
    </div>
  );
};
 
export default BottomSheet;

공식 문서를 따라 useRef 가 아닌 useCallback 을 이용하여 ref를 지정해주어, 구성요소가 나중에 생성된 노드라 하여도 해당 노드에 대한 알림을 받아 값을 업데이트할 수 있게 된다.

마치며

따라서 useRef는 값이 변경되더라도 리렌더링을 발생시키지 않기 때문에, ref를 DOM 노드에 연결하여 필요한 값을 받아야 받아야 한다면 useRef가 아니라 useCallback을 이용하도록 하자.