개발👩‍💻/프론트엔드

react: intersection observer 사용기

gigibean 2022. 3. 31. 21:40
728x90

프로젝트에서 내비게이션바를 만들기 위해서 해당 컴포넌트에 위치하면 내비가 바뀌는 부분을 해야했다.

 

컴포넌트는 가변적이며, 버튼으로 줄였다 늘렸다할 수 있는 공통 컴포넌트를 사용하면 컴포넌트 7가지가 있고

그 컴포넌트들에 따라서 observer가 뷰포트에 위치하는 컴포넌트를 찾아 내비게이션 애니메이션 효과를 주면 되었다.

 

우선 내비게이션을 클릭하면 해당 컴포넌트로 이동하는 부분은 href #로 해결했다.

 

7개의 컴포넌트가 사용하는 공통 컴포넌트에서

// Nav.tsx
...
<a href={`#${id}`}>
...

// CommonContainer.tsx
...
<Container id={id} />
...

이렇게 처리해주었다.

 

하지만 헤더가 있었기에 해당 헤더만큼 숨겨진 마진값을 주어야 했다.

이부분은

// CommonContainer.tsx
const CommonWrapper = styled.div`
  display: block;
  padding-top: hiddenTop;
  margin-top: calc(space - hiddenTop);
  ...
`;

space는 컴포넌트 top의 간격만큼 주고 hiddenTop은 헤더만큼 주었다

 

Observer 사용하기

 

옵저버 커스컴 훅을 만들었다.

import { useCallback, useEffect, useMemo, useRef, useState } from "react";

const useIntersect = (onIntersect, option) => {
  const target = useRef<Element>();
  const [isView, setIsView] = useState(false);

  const checkIntersect = useCallback(([entry], observer) => {
    if (entry.isIntersecting) {
      onIntersect(entry, observer);
      setIsView(true);
    } else {
      setIsView(false);
    }
  }, []);

  useEffect(() => {
    let observer: IntersectionObserver;
    if (target.current) {
      observer = new IntersectionObserver(checkIntersect, {
        ...option,
      });
      observer.observe(target.current);
    }
    return () => observer && observer.disconnect();
  }, [target, option?.root, option?.threshold, option?.rootMargin, checkIntersect]);

  return [target, isView];
};

export default useIntersect;

가지고올 땐

const onIntersect = useCallback((entry, observer) => {
    // callback code
  }, []);

  const options = {
    rootMargin: "2px",
  };

  const [target] = useIntersect(onIntersect, options);

이와 같이 사용할 수 있다

 

여기서 조금 헤맸는데

 

옵션을 설정하기 위해서 rootMargin, threshold 를 설정해 주었다.

 

문제는 threshold는 가변적이기 때문에 상대적으로 길거나 짧은 컴포넌트가 들어오면 지정된 threshold 내에 설정되지 못할 수 있다.

예를들어 0.5로 설정해주었지만 해당 컴포넌트가 뷰포트 전체를 덮어도 0.01 밖에 안될 수도 있고 그 반대의 경우도 있다는 것이다.

 

이럴 땐 thredhold 옵션을 빼주면 된다. 

 

잘못 옵션처리를 했다가 여러개 ref가 걸리기도 하고 제대로 인식하지 못할 수도 있다.

 

버튼을 누르면 굉장히 컴포넌트가 길어지기 때문에 이부분에 대해서 callback ref를 사용해보기도 했지만

 

그냥 옵션이 아예없는게 가장 자연스럽게 보였다.

 

그리고 observe된 컴포넌트 id를 스토어로 관리하여 nav에서 사용할 수 있도록 했다.

 

 

# ****


ref

https://ko.reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node


https://tecoble.techcourse.co.kr/post/2021-05-15-react-ref/



이는 리액트가 제공하는 재조정, 랜더링과 같은 feature들을 이용하지 않는 것으로 순수 JS를 제어하는데 사용한다.

실제 DOM에 React 노드가 렌더될 때가지 ref가 가리키는 DOM요소의 주소 값은 확정된 것이 아니다.

그리고 가상 돔이 변경될 때 실제 DOM의 요소도 변경되는 경우가 있기 때문에 DOM이 업데이트 되는 경우도 ref의 current값이 변경된다.

 

 

Hook 자주 묻는 질문 – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

React ref 톺아보기

0. Intro React로 웹프론트엔드 개발을 하다보면 React만으로는 DOM을 조작하기 어려울 때가 있습니다. 가장 흔하게는 어떤 엘리먼트를 focus 해야 할 때 말이죠. React의 ref는 무엇이고, ref…

tecoble.techcourse.co.kr

 

평소 useRef, useEffect, useState를 사용해야 했던 부분을 callback ref으로도 할 수 있었다.

 

// 기존 코드

...
const [height, setHeight] = useState<number>();

const target = useRef<HTMLElement>();

useEffect(() => {
	if (target.current) {
    	setHeight(target.current.getBoundingClientRect().height);
    }
}, [])

return (
...
	<div ref={target}>
...
)
...

// use callback ref
...
const [height, setHeight] = useState<number>();

const callbackTarget = useCallback((node) => {
	if (node) {
    	setHeight(node.getBoundingClientRect().height);
    }
}, [])

return (
...
	<div ref={callbackTarget}>
...
)
...

ref에 함수를 넘겨서 마운트시 해당 해당 코드를 콜백하게 된다

반응형