무한스크롤 react 만들어보기

미마모코딩·2022년 10월 18일
0

무한스크롤

목록 보기
2/2
post-thumbnail

오늘은 지난시간과 다르게 react로 무한스크롤을 구현해보자.

소스코드는 다음과 같다

CUSTOMHOOK

import axios from "axios";
import { useState, useEffect } from "react";

const useBookSearch = (query, pageNumber) => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [hasMore, setHasMore] = useState(false);
  const [books, setBooks] = useState([]);

  useEffect(() => {
    setBooks([]);
  }, [query]); //쿼리가 바뀔때마다 빈배열로 초기화 하고

  useEffect(() => {
    //여기서 데이터 패칭 다시 하는거
    setLoading(true);
    setError(false);
    let cancel;
    axios
      .get("http://openlibrary.org/search.json", {
        params: { q: query, page: pageNumber },
        cancelToken: new axios.CancelToken((c) => (cancel = c)),
      })
      .then((res) => {
        setBooks((prevBooks) => {
          //업데이트함수
          return [
            ...new Set([
              //중복제거
              ...prevBooks, //업데이트함수의 초기값
              ...res.data.docs.map((book) => book.title), //내용을 추가하는거
            ]),
          ];
        });
        setHasMore(res.data.docs.length > 0);
        setLoading(false);
      })
      .catch((e) => {
        if (axios.isCancel(e)) return;
        setError(true);
      });
    return () => cancel();
  }, [query, pageNumber]);
  return { loading, error, books, hasMore };
};

export default useBookSearch;

위와같이 커스텀 훅을 사용해 만들었다.

axios를 통해 데이터를 패칭해오고 여러가지 상태를 만들었다.

로딩,에러,더 보여줄건지,데이터를 담아놓을 공간을 말이다.

먼저 첫번째 useEffect의 의존성배열안에 쿼리를 넣어두었다 . 눈치가 빠른 사람들은 알아차렸을것이다.

바로 저 커스텀 훅을 상위 컴포넌트에서 호출하고 그 함수가 차례대로 진행되게끔 , 또한 쿼리랑 pageNumber를 전달해 상태를 변경시켜 useEffect 훅을 반복적으로 호출하게 만들것이란걸 말이다.

위의 axios의 캔슬토큰 만들기는 내 이전 블로그를 참고하길 바란다.(캔슬토큰이란?)axios요청을 멈추기위한 개념이라고 보면 된다.

그리고 then문에서 res를 받아 업데이트 함수를통해 데이터를 업데이트해준다. state가 바뀌었으니 컴포넌트를 호출할것이고 마운트 언마운트가 계속 이루어지면서 불필요하게 데이터 패칭을 하는것을 막기위해 쿼리가 바뀔때 즉 언마운트 되기 직전에 클린업 함수를 호출하게된다.

클린업의 개념은 내 이전 블로그 useEffect편을 보고 예제코드를 반드시 연습해보길 바란다. 또한 new Set을 통해 중복을 제거한다.

hasMore의 내용은 사실 코드구문을 봤을때 응답이 성공적으로 이루어지고 쿼리를 통한 검색 즉 res.data.docs(제목임).length가 0보다 클때 즉 데이터를 받아오기만 하면 truthy한 값을 내뱉어 계속 반복적으로 데이터를 패칭하겠다는 이야기다.

appCompo

import React, { useState, useRef, useCallback } from "react";
import useBookSearch from "./useBookSearch";

export default function App() {
  const [query, setQuery] = useState("");
  const [pageNumber, setPageNumber] = useState(1);

  const { books, hasMore, loading, error } = useBookSearch(query, pageNumber);
                 //구조분해하면서                    //함수호출 

  const observer = useRef(); //빈값으로 남겨둠 그리고 이후에 데이터를 넣어줄거임 Current로
  const lastBookElementRef = useCallback(
    (node) => {
      if (loading) return; //로딩중이면 아직받아오지않고 종료
      if (observer.current) observer.current.disconnect(); //커런트가 없기때문에 이 구문은 통과함 하지만 커런트가 생기고
      //마지막 요소가 잡히면 다음 인터섹팅이 이루어질때 삭제함
      observer.current = new IntersectionObserver((entries) => {
         if (entries[0].isIntersecting && hasMore) {
          setPageNumber((prevPageNumber) => prevPageNumber + 1);
        } //위코드는 현재 entries[0]을 감시하고 hasMore(데이터를 받아왔다면) 
          //페이지넘버를 업데이트 함수로갱신하고
      });
      if (node) observer.current.observe(node); //노드를 추적한다 . 즉 마지막 요소를 추적함
           //추적이 이미 끝난 녀석들은 위 useCallback이 다시 호출될때 
          //if (observer.current) observer.current.disconnect();구문을 통해 추적되지않음
    },
    [loading, hasMore] //로딩중이고 hasMore은 데이터를 받아오기만 하면 무조건 true가 되게 설계됨
  );
  //함수자체가 무겁기때문에 콜백으로 메모이제이션 가능하게하고 
  // loading, hasMore가 바뀔때마다 계속 

  function handleSearch(e) {
    setQuery(e.target.value);
    setPageNumber(1);
  }

  return (
    <>
      <input type="text" value={query} onChange={handleSearch}></input>
      {books.map((book, index) => {
        if (books.length === index + 1) { //마지막요소의 인덱스번호를 찾는거
          return (
            <div ref={lastBookElementRef} key={book}>
              {book}
            </div>
          );
        } else {
          return <div key={book}>{book}</div>; //일반요소
        }
      })}
      <div>{loading && "Loading..."}</div>
      <div>{error && "Error"}</div>
    </>
  );
}

app컴포넌트는 위에서 말했듯이 useBookSearch를 query,pageNumber를 넣어 호출한다.

위 주석을 보고 한 줄 한 줄 이해해보고 이전시간에 포스팅한 바닐라js로 무한스크롤 만들기 편을 꼭 보고 오길 바란다.

react에서의 무한스크롤은 생각보다 난이도가 쉽지많은 않다.

필연적으로 useEffect 클린업 , 업데이트함수의 개념을 알아야하고
ref로 dom을 잡는것도 필요하다.
또한 IntersectionObserver의 기본적인 동작원리와 여러 상태를 useState로 관리해 적절한 시기에 setState를 통해 상태를 변경시켜주어야한다.

0개의 댓글