๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŒˆ ํƒ€๋ฏธ๋…ธํŠธ

TIL #1 ๋น ๋ฅด๊ณ  ๊ฐ„ํŽธํ•˜๊ฒŒ React ๋กœ InfiniteScroll (๋ฌดํ•œ ์Šคํฌ๋กค) ๊ตฌํ˜„ํ•˜๊ธฐ

by Tamii 2021. 7. 28.
๋ฐ˜์‘ํ˜•

React๋กœ InfiniteScroll์„ ํ•˜๋Š” ๋ฐฉ๋ฒ• 2๊ฐ€์ง€

  1. onScroll ์ด๋ฒคํŠธ
  2. Intersection Observer API

 

 

 

onScroll๊ณผ ๊ทธ ํ•œ๊ณ„


์‚ฌ์šฉ์ž์˜ scroll event๋ฅผ ๊ณ„์† ๋ณด๋ฉฐ ํŽ˜์ด์ง€๊ฐ€ ๋์— ์˜ค๋Š”์ง€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ตฌํ˜„์ด๋‹ค. ํ•˜์ง€๋งŒ
๋“œ๋ฅด๋ฅต ์Šคํฌ๋กค ํ• . ๋•Œ๋งˆ๋‹ค ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

ํฌ๋กฌ์—๋‹ค ์ด๊ฒƒ๋งŒ ์ณ๋ด๋„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

 

window.addEventListener('scroll',()=>console.log('๐ŸŒ€'))

 

 

 

 

 

๊ทธ๋ ‡๋‹ค๋ฉด ๋‚จ์€ ๊ฒƒ์€

๐Ÿฅณ Intersection Observer API

ํƒ€๊ฒŸ๊ณผ viewPort ์‚ฌ์ด์˜ intersection ๋ณ€ํ™”๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๊ด€์ฐฐํ•˜๋Š” ๋ฐฉ๋ฒ•

  • LazyLoading
  • Infinite-scroll
  • ์‚ฌ์šฉ์ž ๊ฒฐ๊ณผ ์—ฌ๋ถ€์— ๋”ฐ๋ฅธ ์• ๋‹ˆ๋ฉ”์ด์…˜

๋“ฑ์— ์‚ฌ์šฉ๋œ๋‹ค.

์—ฌ๊ธฐ์„œ ๋‚˜๋Š” Infinite-scroll์„ ๋งŒ๋“ค๋ฉฐ API๋ฅผ ๊ฒช์–ด๋ณด์•˜๋‹ค.

 

 

Infinite-Scroll ๊ตฌํ˜„ ์ˆœ์„œ

  1. ์Šคํฌ๋กคํ•  Component ๊ตฌ์„ฑ
  2. Intersection observer ์ƒ์„ฑ
  3. ์ƒ์„ฑํ•œ Intersection observer ๋ถ™์—ฌ์ฃผ๊ธฐ
  4. useFetch ์ƒ์„ฑ (data๊ฐ€ API์š”์ฒญ์ผ ๋•Œ)

์Šคํฌ๋กคํ•  ๋Œ€์ƒ์„ ๋งŒ๋“  ํ›„
Intersection observer๋ฅผ ๋งŒ๋“ค์–ด์„œ ref๋กœ ๋๋‚˜๋Š” ์ง€์ ์— ๋ถ™์—ฌ์ค„ ๊ฒƒ์ด๋‹ค!
ํ˜„์žฌ data๋Š” 10๊ฐœ์”ฉ API query ์š”์ฒญํ•˜์—ฌ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์žˆ๋‹ค.

ํŒŒ์ผ์€ ํฌ๊ฒŒ ๋‘๊ฐ€์ง€
- useFetch(์ปค์Šคํ…€ fetch Hook)
- CardList(์Šคํฌ๋กคํ•  component)

 

 

 

1) ์Šคํฌ๋กคํ•  Component ๊ตฌ์„ฑ

CardContainer ๊ฐ€์žฅ ์•„๋ž˜์— ๋นˆ div๋ฅผ ์ƒ์„ฑํ•ด ref๋ฅผ ๋‹ฌ์•„์ค€๋‹ค.
์ด๊ณณ์—์„œ ๊ต์ฐจ์‹œ์ ์„ ์•Œ์•„๋ณผ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ

 

<CardListContainer>
      {list?.map((card) => (
        <Cardd>{card.id}</Cardd>
      ))}
      <div ref={observer} />
      <>{isLoading && <Loading />}</>
</CardListContainer>

 

2) Intersection observer ์ƒ์„ฑ (์˜ˆ์‹œ)

์ฐธ๊ณ ) options๋Š” ์˜ต์…˜์‚ฌํ•ญ์ด๋ผ ์˜ˆ์‹œ์ฝ”๋“œ์—์„œ๋Š” ๋”ฐ๋กœ ๋„ฃ์ง€ ์•Š์•˜๋‹ค.

let options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

let observer = new IntersectionObserver(callback, options);

root : ์Šคํฌ๋กค ํ•  ๋Œ€์ƒ์„ ๊ฐ์‹ผ ์š”์†Œ , ์Šคํฌ๋กค ํ•  ๊ณณ
null ํ˜น์€ default = viewPort
_
rootMargin : root ๊ฐ€ ๊ฐ€์ง„ ์—ฌ๋ฐฑ
_๋ฌธ์ž์—ด๋กœ ๋„ฃ๊ธฐ

threshold : target์ด root์™€ ๋ช‡% ๊ต์ฐจํ–ˆ์„ ๋•Œ callback ์„ ์‹คํ–‰ํ•  ์ง€ ๊ฒฐ์ •ํ•˜๋Š” ๊ฐ’
0.0~1.0

 

3) ์ƒ์„ฑํ•œ Intersection observer ๋ถ™์—ฌ์ฃผ๊ธฐ

pageNum:ํŽ˜์ด์ง€๋ฒˆํ˜ธ๋ฅผ query ๋กœ API ์š”์ฒญํ•ด data๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ๋•Œ๋ฌธ์— pageNum์„ useFetch์— ๋„˜๊ฒจ์คŒ
list : ๋ฟŒ๋ ค์ค„ data
isLoading: ๋กœ๋”ฉ์ค‘์ธ์ง€ boolean
hasMore : data ๋” ๊ฐ€์ ธ์˜ฌ ๊ฒŒ ๋‚จ์•˜๋Š”์ง€

 

 

< CardList.jsx >

const CardList = () => {
  const [pageNum, setPageNum] = useState(1);
  const { list, hasMore, isLoading } = useFetch(pageNum);
  const observerRef = useRef();

  const observer = (node) => {
    if (isLoading) return;
    if (observerRef.current) observerRef.current.disconnect();

    observerRef.current = new IntersectionObserver(([entry]) => {
      if (entry.isIntersecting && hasMore) {
        setPageNum((page) => page + 1);
      }
    });

    node && observerRef.current.observe(node);
  };

  return (
    <CardListContainer>
      {list?.map((card) => (
        <Cardd>{card.id}</Cardd>
      ))}
      <div ref={observer}/>
      <>{isLoading && <Loading />}</>
    </CardListContainer>
  );
};
export default CardList;

 

๋ฉ”์ธ์ด ๋˜๋Š” ๋ถ€๋ถ„๋งŒ ์ฃผ์„์„ ๋‹ฌ์•„ ์„ค๋ช…ํ•˜์ž๋ฉด

const observerRef = useRef();

  const observer = (node) => {
    // 1.๋กœ๋”ฉ์ค‘์ด๋ฉด return
    if (isLoading) return;
    //onserverRef.current๊ฐ€ ์žˆ์œผ๋ฉด ๋Š๊ธฐ
    if (observerRef.current) observerRef.current.disconnect();
    //observerRef.current ์ƒ์„ฑ
    observerRef.current = new IntersectionObserver(([entry]) => {
      // entry ๊ฐ€ ๊ต์ฐจ๋˜์—ˆ์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ๊ฐ€ ๋” ์žˆ์œผ๋ฉด ํŽ˜์ด์ง€ 1์ฆ๊ฐ€
      if (entry.isIntersecting && hasMore) {
        setPageNum((page) => page + 1);
      }
    });

    //
    node && observerRef.current.observe(node);
  };

 

4) useFetch ์ƒ์„ฑ

< useFetch.jsx>

import React, { useState, useEffect, useCallback } from 'react';

const END_POINT = 'https://';
const useFetch = (page) => {
  const [list, setList] = useState([]);
  const [hasMore, setHasMore] = useState(false);
  const [isLoading, setIsLoading] = useState(false);

  //query API ์š”์ฒญ ๋ณด๋‚ด๊ธฐ
  const sendQuery = useCallback(async () => {
    const URL = `${END_POINT}?${page}~~~`;

    try {
      setIsLoading(true);
      const response = await (await fetch(URL)).json();
      if (!response) {
        throw new Error(`์„œ๋ฒ„์— ์˜ค๋ฅ˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.`);
      }
      setList((prev) => [...new Set([...prev, ...response])]);
      setHasMore(response.length > 0);
      setIsLoading(false);
    } catch (e) {
      throw new Error(`์˜ค๋ฅ˜์ž…๋‹ˆ๋‹ค. ${e.message}`);
    }
  }, [page]);

  useEffect(() => {
    sendQuery();
  }, [sendQuery, page]);

  return { hasMore, list, isLoading };
};
export default useFetch;
// list. data ์ „์— ์žˆ๋˜ ๊ฐ’ + ์ƒˆ๋กœ ๊ฐ€์ ธ์˜จ query API ์‘๋‹ต๊ฐ’์„ ๋”ํ•ด ์ƒˆ๋กœ์šด ๋ฐฐ์—ด์„ ๋งŒ๋“ค์–ด set
      setList((prev) => [...prev, ...response]);
//์‘๋‹ต๊ฐ’์ด ์žˆ์œผ๋ฉด set
      setHasMore(response.length > 0);
// data ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์ „ ์ž ์‹œ ๋กœ๋”ฉ์ค‘
    setIsLoading(true);
// ์ค‘๋žต
    setIsLoading(false);

+ ์ •๋ณด ์ถ”๊ฐ€ 

  const [isLoading, setIsLoading] = useState(false);

์ด Loading state๋Š” ๋กœ๋”ฉ ๊ตฌํ˜„์‹œ์—๋งŒ ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ,

์•„๋‹ˆ์—ˆ๋‹ค.  ์ต๋ช…์˜ ๋ช…0๋‹˜๊ป˜์„œ ์•Œ๋ ค์ฃผ์…จ๋Š”๋ฐ Loading State์—†์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด data๊ฐ€ fetch ํ•˜๋Š” ๋™์•ˆ ์˜ต์ €๋ฒ„๊ฐ€ useRef๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ํŽ˜์ด์ง€ ์ˆ˜๋ฅผ ์ฆ๊ฐ€์‹œํ‚ค๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. 

๋”ฐ๋ผ์„œ  loading์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•„๋„ ๋ฌดํ•œ์Šคํฌ๋กค์—์„œ๋Š” ํ•ญ์ƒ LoadingState๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค!

๋ผ๋Š” ๊ฒฐ๋ก ์„ ๋„์ถœํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค~!~!!!!โœจ  ์ด์ž๋ฆฌ๋ฅผ ๋นŒ์–ด 0์ง„๋‹˜ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๐Ÿ˜

 

 

๊ตฌํ˜„ ๊ฒฐ๊ณผ

๋ฌดํ•œ ์Šคํฌ๋กค

๋กœ๋”ฉ์ค‘


 

 

 

โœจ ์—ฌ๊ธฐ์„œ ๋ณด๋„ˆ์Šค

useCallback

React ์—์„œ component๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ๋งˆ๋‹ค ๋‚ด๋ถ€์— ์„ ์–ธ๋œ ํ‘œํ˜„์‹๋„ ๋‹ค์‹œ ์„ ์–ธ๋˜๋Š”๋ฐ, ์ด๊ฒƒ์€ ๋งค์šฐ ๋‚ญ๋น„
์ด๊ฒƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด useCallback์„ ์‚ฌ์šฉํ• ์ˆ˜ ์ž‡๋Š”๋ฐ
1์งธ ๋งˆ์šดํŠธ ๋  ๋•Œ ํ•œ๋ฒˆ๋งŒ ์„ ์–ธํ•˜๊ณ  ๊ทธ ์ดํ›„๋กœ ์žฌ์‚ฌ์šฉํ•˜์ž!

  • ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ํ•จ์ˆ˜
  • api๋ฅผ ์š”์ฒญํ•˜๋Š” ํ•จ์ˆ˜์— ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
     
    ๋ช…์‹ฌํ•˜์ž, ๊ทธ๋ž˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋‹ค๋ˆ์ด๋‹ค~

์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ1
์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ2
์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ3

https://velog.io/@tami/TIL1-React-%EB%A1%9C-InfiniteScroll-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0

ํ‹ธ๋žœ๋“œ์—๋„ ์˜ฌ๋ผ๊ฐ ^^

 

TIL#1 -๋น ๋ฅด๊ณ  ๊ฐ„ํŽธํ•˜๊ฒŒ React ๋กœ InfiniteScroll (๋ฌดํ•œ ์Šคํฌ๋กค) ๊ตฌํ˜„ํ•˜๊ธฐ

React๋กœ InfiniteScroll์„ ํ•˜๋Š” ๋ฐฉ๋ฒ• 2๊ฐ€์ง€ onScroll ์ด๋ฒคํŠธ Intersection Observer API onScroll์˜ ํ•œ๊ณ„ ![](https://images.velog.io/images/tami/post/7996be3b-0366-4cd

velog.io

 

๋Œ“๊ธ€