Skip to content

Week 4 Infinite Scroll

SeonJun Hwang edited this page Nov 26, 2019 · 1 revision

Infinite Scroll

  • Pagination을 이용하는 한 기법
  • 스크롤을 기준으로 하단에 스크롤이 도착하면 기준에 부합하는 다음 페이지를 읽어오는 기법

React에서 어떻게 구현하지?

Scroll의 위치를 직접 계산해서 끝에 도착하면 fetch 작업을 통해 진행

  • 계산을 하기 때문에 원하는 타이밍에 fetch작업을 진행
  • 코드만 봐도 상당히 직관적인 작업
  • dom객체에 직접 접근한다는 점이 react에 맞는 점인지는 의문 🤔
    const curScroll // 현재 스크롤의 위치 ( 스크롤이 시작하는 위치 기준 )
    const scrollHeight // 스크롤의 길이
    const windowTotalHeight // 스크롤 전체의 길이 

    // 무한스크롤 조건
    if (curScroll + scrollHeight === windowTotalHeight)
    fetch(...) 

확실히 누가봐도 코드가 상당히 직관적이다.
하지만 scroll Event는 자주 발생 + 비용이 높다.
즉 사용하면 사용할수록 성능이 저하되고 렌더링이 깔끔하지 못하다.

Intersection Observer를 이용하여 특정 위치에 도착하면 fetch 작업을 진행

  • 비동기로 작업 처리
  • 특정 컴포넌트가 노출되는 정도에 따라 사용자 지정 작업을 실행
  • Scroll 관련 연산이 적어 Cost가 적다
  • 비동기기 때문에 렌더링이 깔끔하고 스크롤이 부드러움
  • Custom Hook을 사용해야 한다 ( 왜인지는 잘 모르겠다 )

useIntersection.jsx

// Custom Hook
import { useState, useEffect, useCallback } from "react";

// 기본 옵션, 목표 컴포넌트의 50%가 보이면 실행된다.
const baseOption = {
  root: null,
  threshold: 0.5,
  rootMargin: "0px"
};

const useIntersect = (onIntersect, option) => {
  const [ref, setRef] = useState(null);
  const checkIntersect = useCallback(([entry], observer) => {
    if (entry.isIntersecting) {
      onIntersect(entry, observer);
    }
  }, []);

  useEffect(() => {
    let observer;
    if (ref) {
      observer = new IntersectionObserver(checkIntersect, {
        ...baseOption,
        ...option
      });
      observer.observe(ref);
    }
    return () => observer && observer.disconnect();
  }, [ref, option.root, option.threshold, option.rootMargin, checkIntersect]);
  return [ref, setRef];
};

export default useIntersect;

infiniteScroll.jsx

//Intersection Components를 사용하는 부분
//작업을 비동기로 처리한다.
  const [_, setRef] = useIntersect(async (entry, observer) => {
    observer.unobserve(entry.target)

    const nextData = await fetcher()
    const nextComponents = drawer(nextData)
    setList(prev => [...prev, ...nextComponents])

    if (nextData.length) observer.observe(entry.target)
  }, {})

Infinite Scroll 라이브러리로 만들 수는 없을까?

  • Android의 ListBox, RecylcerView 처럼 Custom Component로 넣을 수는 없나?
  • 특정 위치에 도착하면 이벤트가 발생하는건 확정, 무엇을 받아야 할까?
  • 외부에서 render된 컴포넌트를 받으니 hooks 규칙 위반, 그렇다면?
  • data와 component를 그리는 drawer로 나눠서 받는다.
// 컴포넌트
// data를 읽어오는 fetcher랑 drawer로 나눠서 받는다.
const Component = ({ fetcher, drawer }) => {
  const [loadding, setLoadding] = useState(true)
  const [list, setList] = useState([])

  useEffect(() => {
    ;(async () => {
// data를 fecther로 읽어온다.
      const data = await fetcher()
// 읽어온 정보를 drawer에 넘겨 component로 바꿔준다.
      const initComponents = drawer(data)
      setLoadding(false)
// 바꾼 내용을 내부 상태에 넣어주면? Hooks 문제 없이 정상적으로 받아진다.
      setList(prev => [...initComponents])
    })()
  }, [])

생각해봐야 할 점

  • 외부에서 render된 후 해당 컴포넌트를 넘겨 받아 처리하려고 하니 Hooks rule 위반이다. 왜일까?
  • 재사용성 과연 이게 높아진걸까?
  • 나름 라이브러리처럼 만들어서 따로 뺄 수 있을것 같다. 과연?