Intersection Observer API

: 특정 요소가 viewport와 교차하는 순간을 감지해 콜백함수를 실행함

const targetElement = document.getElementById('myElement');
const options = {
  root: null, // viewport를 기준으로 관찰
  rootMargin: '0px',
  threshold: 0.5 // 요소가 50% 이상 보일 때 이벤트 발생
};

const observer = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('요소가 화면에 보입니다.');
      // 여기에 원하는 동작을 추가합니다. (예: 이미지 로딩, 애니메이션 시작 등)
    } else {
      console.log('요소가 화면에서 사라졌습니다.');
    }
  });
}, options);

observer.observe(targetElement);

Troubleshooting #1

상황

  1. Observer Instance 생성후 isIntersecting true가 되면 callback을 실행했다.
  useEffect(() => {
    const target = observerRef.current;
    const observer = new IntersectionObserver((item) => {
      if (item[0].isIntersecting) {
        **callback()**;
      }
    });
    if (!target) return;
    observer.observe(target);
    return () => {
      observer.unobserve(target);
    };
  }, []);

callback

 const [pageNum, setPageNum] = useState(0);
 
  const callback =async () => {
    try {
      setIsLoading(true);
      const { datas, isEnd } = await getMockData(MOCK_DATA, pageNum);
      if (isEnd) {
        return;
      } else {
        setRenderData((prev) => [...prev, ...datas]);
        setPageNum((prev) => prev + 1);
      }
    } catch (error) {
      console.log(error);
    } finally {
      setIsLoading(false);
    }
  }

스크린샷 2024-10-15 오후 3.15.23.png

callback함수가 재 실행될때 리스트 렌더링이 되는 부분에서 같은 키를 가졌다고 오류가 났다

→ 같은 페이지의 데이터를 불러왔다라는 뜻으로 알 수 있다.

Oct-15-2024 15-24-28.gif

그렇다고 pageNum이 변경되지 않은 건 아니다.

→ callback 함수 내부에 문제점이 있다..

원인

클로저 : 함수가 생성될 당시의 환경에서 사용된 변수를 참조

변수 그 자체를 기억하는 것이 아니라, 변수가 가리키는 값에 대한 참조를 함 ( = 포인터 처럼 주소를 캡쳐함)

<aside> 💡

  1. callback 함수가 생성될 당시의 pageNum(=useState의 initial Value) 값을 캡쳐함
  2. useState 로 관리되는 상태는 변경될 때마다 새로운 값을 할당 받음 → 하지만 클로저는 초기 값의 메모리 주소를 캡처했기 때문에 변경된 주소를 알지 못함

</aside>

해결 → useRef 사용

<aside> 💡

callback 함수 내부에 let pageNumRefCurrent = pageNumRef.currnet 선언 후 할당은 위와 같은 오류가 남

→ 당연함 ! pageNumRefCurrent의 메모리 주소는 초기값만을 가리킬테니

</aside>

  const callback = async () => {
    try {
      setIsLoading(true);
      **const { datas, isEnd } = await getMockData(MOCK_DATA, pageNumRef.current);**
      if (isEnd) {
        return;
      } else {
        console.log("callback함수 실행될때의 pageNum ", pageNumRef.current);
        setRenderData((prev) => [...prev, ...datas]);
        **pageNumRef.current = pageNumRef.current + 1;**
      }
    } catch (error) {
      console.log(error);
    } finally {
      setIsLoading(false);
    }
  };