6 . ๐ŸŽ‰ Intersection Observer + React ๋ฌดํ•œ์Šคํฌ๋กค ๊ตฌํ˜„

Eunsuยท2021๋…„ 12์›” 30์ผ
4

@ React

๋ชฉ๋ก ๋ณด๊ธฐ
6/11

๋ชจ๋ฅด๋Š”๊ฑด ๋ฐฐ์šฐ๊ณ  ๋ฐฐ์› ์œผ๋ฉด ์จ๋ด์•ผ ๋ญ๊ฐ€ ๋ญ”์ง€ ์•”..
๊ทธ๋ž˜์„œ ์–ด์ œ ํฌ์ŠคํŒ…ํ•œ Intersection Observer ๋ฅผ ์‚ฌ์šฉํ•ด ๋ฌดํ•œ ์Šคํฌ๋กค์„ ๊ตฌํ˜„ํ•ด ๋ณด๊ธฐ๋กœ ํ•จ.

์ด๋ถ„๊ฐ•์˜๋ฅผ ๋“ฃ๊ณ  ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ํฌ์ŠคํŒ…ํ–ˆ์Œ. (์ด๋ฏธ์ง€ ํด๋ฆญ ์‹œ ์œ ํŠœ๋ธŒ ์ด๋™ํ•จ.)

๐Ÿš€ Intersection Observer Infinite Scrolling

1. Unsplash API ์„ค์ •

unsplash์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ฌด๋ฃŒ API์‚ฌ์šฉํ•จ. ํšŒ์›๊ฐ€์ž… ํ•˜๋ฉด ๋ฌด๋ฃŒ๋กœ API ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ!

https://unsplash.com/developers

ํšŒ์›๊ฐ€์ž…ํ•˜๊ณ  ๋กœ๊ทธ์ธํ•ด์„œ ๋…ธ๋ž€๋ถ€๋ถ„์„ ํด๋ฆญํ•˜๋ฉด

์š”๋ ‡๊ฒŒ API ํ‚ค๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ

Documentation์— ๋…ธ๋ž€๋ถ€๋ถ„์„ ํด๋ฆญํ•˜๋ฉด API์‚ฌ์šฉ ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ์Œ. ๊ทธ์™ธ API ๋ฉ”์„œ๋“œ ๋“ค๋„ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ

ํŽ˜์ด์ง€์™€ ๋ช‡๊ฐœ์˜ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š”์ง€ ์ปจํŠธ๋กค ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด 10๊ฐœ๋งŒ ๋ณด์—ฌ์ค„ ๊บผ์ž„.

2. ์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑ

โ—พ CSS / component ๊ตฌ์„ฑ

  • public
  • src
    - index.js
    - styles.css
    - App.js
    - PhotoItem.js

CSS ์นดํ”ผํ•จ. ์ด ๋ถ„ github์€ ์œ ํŠœ๋ธŒ ์‚ฌ์ดํŠธ์— ์žˆ์Œ.

3. ๋ฐ์ดํ„ฐ ํŒจ์นญ

โ—พ App.js

import axios from "axios";
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
  const [photos, setPhotos] = useState([]);
  //๋ฐ์ดํ„ฐ ๊ณ„์†ํ•ด์„œ ๋‹ด์„ state
  const [pageNumber, setPageNumber] = useState(1);
  //์Šคํฌ๋กค์ด ๋‹ฟ์•˜์„ ๋•Œ 2=>3=>4 ์ƒˆ๋กญ๊ฒŒ ๋ฐ์ดํ„ฐํŽ˜์ด์ง€๋ฅผ ๋ฐ”๊ฟ€ state
  const [loading, setLoading] = useState(false);
  //๋กœ๋”ฉ ์„ฑ๊ณต, ์‹คํŒจ ๋‹ด์„ state
  const fetchPhotos = async () => {
    const API_KEY = "mEbcH0pSm70nidrKQS43hkgPtYQeFj1GI1txLml1tmk";
     await axios
      .get(
        `https://api.unsplash.com/photos/?client_id=${API_KEY}&page=${pageNumber}&per_page=10`
      )
      .then((res) => setPhotos([...photos, ...res.data]))
      //๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ณด์—ฌ์ค˜์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, operator ์—ฐ์‚ฐ์ž๋กœ ๋ณต์ œํ•ด์„œ ์ถ•์ ์‹œํ‚ด.
      .then(() => setLoading(true))
  };
  console.log(photos);
  useEffect(() => fetchPhotos(), [pageNumber]);
  //ํŽ˜์ด์ง€ ๋„˜๋ฒ„๊ฐ€ ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™€์•ผ ํ•˜๋‹ˆ๊นŒ ๋ฐฐ์—ด ๊ฐ’์œผ๋กœ pageNumber๋ฅผ ๋„ฃ์Œ.
  return (
    <div className="App">
      <h1>Infinite Scrolling</h1>
      <button>Load More</button>
    </div>
  );
}


10๊ฐœ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ˆ์˜๊ฒŒ ์ž˜ ๋‹ด๊ฒจ์žˆ์Œ.

4. photos ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ mapping

//App.js
{...์ƒ๋žต}
    <div className="App">
      <h1>Infinite Scrolling</h1>
      {
        photos.map((photo,index) => <PhotoItem key={index} photo={photo}  />)
      }
      <button>Load More</button>
    </div>
//PhotoItem.js
export default function PhotoItem({ photo }) {
  return (
    <div className="photos">
      <img src={photo.urls.small} />
      <p>Photo By : {photo.user.username}</p>
    </div>
  );
}

5. pageNumber ๋ฐ”๊พธ๋Š” ํ•จ์ˆ˜ ๋งŒ๋“ค๊ธฐ

  • 1-10 ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์™”๋‹ค๋ฉด, pageNumber๋ฅผ 1์”ฉ ๋Š˜๋ ค์„œ 11- 20 ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ด. page api method๋ฅผ 2๋กœ ๋ฐ”๊ฟ”์„œ ๊ฐ€๋Šฅํ•ด์ง.
  • ๊ทธ๋ฆฌ๊ณ  ์ƒˆ๋กญ๊ฒŒ ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๋Š” fetchPhotos๋ฅผ ํƒ€๊ณ  ๊ธฐ์กด์— ์žˆ๋˜ ๋ฐ์ดํ„ฐ์— ๋”ํ•ด์ง€๊ฒŒ ๋จ.
  • LoadMore ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด pageNumber๋ฅผ 1์”ฉ ๋”ํ•˜๋Š” ํ•จ์ˆ˜ ๋งŒ๋“ฌ.
//App.js
const loadMore= () => setPageNumber(prev => prev+1);
// setState๋Š” parameter๋กœ state๋กœ ๋ฐ›์Œ.

## 6. Target ์„ค์ •

๋ฌดํ•œ ์Šคํฌ๋กค์˜ ์ด๋ก ์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งˆ์ง€๋ง‰ ์—˜๋ฆฌ๋จผํŠธ์— ๋‹ฟ์•˜์„ ๋•Œ, ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ์ด๋‹ค. ๋‚ด๊ฐ€ ๋งŒ๋“  ํŽ˜์ด์ง€์—์„œ ๋งˆ์ง€๋ง‰ ์—˜๋ฆฌ๋จผํŠธ๋Š” `LoadMore`๋ฒ„ํŠผ์ด๋ฏ€๋กœ, ์ด ๋ฒ„ํŠผ์— ref ๋ฅผ ์ง€์ •ํ•ด๋†“๊ณ  ํƒ์ƒ‰ ํƒ€๊ฒŸ์œผ๋กœ ์ •ํ•˜๋ฉด, 
**์ด ์—˜๋ฆฌ๋จผํŠธ์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ๋ญ”๊ฐ€๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค. (๋ฐ์ดํ„ฐ ํŒจ์นญ)**

### โ–ช ref ์ง€์ •
> ```javascript
import {useRef } from "react";
const target= useRef();
{...์ƒ๋žต}
<button ref={target}>Load More</button>

7. Intersection Observer ์‚ฌ์šฉํ•ด์„œ ๋ทฐํฌํŠธ ์•ˆ์—์žˆ๋Š” ์—˜๋ฆฌ๋จผํŠธ ๊ต์ฐจ ํ™•์ธํ•˜๊ธฐ

โ—พ Observer ๋“ฑ๋ก

//App.js
{...์ƒ๋žต}
useEffect(() => {
	if(loading){
    //๋กœ๋”ฉ๋˜์—ˆ์„ ๋•Œ๋งŒ ์‹คํ–‰
    	const observer= new IntersectionObserver((entries) => {
        	console.log(entries[0])
        })
        //์˜ต์ ธ๋ฒ„ ํƒ์ƒ‰ ์‹œ์ž‘
        observer.observe(target.current);
    }
},[])
{...์ƒ๋žต}

viewport ํ•ด๋‹น ๋‚ด์—์„œ ์Šคํฌ๋กค์„ ํ• ๋•Œ ์˜ต์ ธ๋ฒ„๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ ์ฝ˜์†”์ด ์ฐํž˜.

  • ์Šคํฌ๋กค์ด target์— ๋„๋‹ฌํ•˜๊ธฐ ์ „ : isIntersection:false
  • ์Šคํฌ๋กค์ด target์— ๋„๋‹ฌํ•œ ํ›„ : isIntersection:true
//App.js
  useEffect(() => {
    if (loading) {
      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting) {
          loadMore();
          //๋ฒ„ํŠผ์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ pageNumber๋ฅผ 1์”ฉ ์ฆ๊ฐ€์‹œ์ผœ ๋ฐ์ดํ„ฐ๋ฅผ 10๊ฐœ์”ฉ ๋” ๋ณด์—ฌ์คŒ.
        }
      });
      observer.observe(target.current);
    }
  }, []);

8. ํŽ˜์ด์ง€ ์ˆ˜ ์ œํ•œํ•˜๊ธฐ

60 ๊ฐœ์ด์ƒ์˜ ์ฝ˜ํ…์ธ ๋ฅผ ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ•˜๊ณ ์‹ถ์Œ. ๊ทธ๋Ÿฌ๋ฉด 6๋ฒˆ ์ด์ƒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋ฉด ์•ˆ๋จ.

//App.js
{...์ƒ๋žต}
  let num = 1; //๋ณ€์ˆ˜ ์ง€์ •
  useEffect(() => {
    if (loading) {
      const observer = new IntersectionObserver((entries) => {
        if (entries[0].isIntersecting) {
          num++; //๋ณ€์ˆ˜ ์ฆ๊ฐ€์‹œํ‚ด
          loadMore();
          if (num >= 6) {
          	//6์ด์ƒ์ด๊ฑฐ๋‚˜ ๊ฐ™์œผ๋ฉด ํƒ์ƒ‰์ค‘์ง€
            observer.unobserve(target.current);
          }
        }
      });
      observer.observe(target.current);
    }
  }, []);
  {...์ƒ๋žต}

9. ๋!

profile
function = (Develope) => 'Hello World'

0๊ฐœ์˜ ๋Œ“๊ธ€