무한스크롤 더미데이터로 구현

성민개발로그·2022년 2월 22일
0
post-thumbnail

프로젝트 만드는도중에 Effect 데이터를 스크롤이 프로젝트 화면 하단에 내려올때 다시 새로운 데이터를 불러오는 기능을 구현하고자 했다.

본 포스트는 Redux , Redux-saga 에대한 선행지식이 필요하다

1. Reducer 부분 설정

위치 reducers/effect.js

reducer 설정하는곳에 상태값 초기화 설정부터 해 주겠다.

import produce from "immer"; 
// redux 불변성 규칙을 쉽게 컨트롤할 수 있는 유용한 라이브러리
import dummyData from "../dummyData";
//effects 효과가 담긴 더미데이터 
export const initialized = {
    mainEffects:[], // 실제로 화면에 보여질 Effect 객체 데이터들을 담아줄 배열.
    loadEffectsLoading:false, // Effects 데이터들이 불러오는 중인지 아닌지 판단
    loadEffectsDone:false, //Effects 데이터 불러오는데 성공인지 아닌지 판단 
    loadEffectsError:null, // Effects 데이터 불러올때 에러 
    hasMoreEffects:true, 
// 더이상 불러올 Effects 들이 있는지 판단 초기에는 true 값으로 무조건 불러올수 있게
}

// 해당 함수를 통해서 더미데이터에 있는 effects 들을 splice메서드를 통해서 불러옵니다.
const loadMoreEffects = () =>{
    let index =0;
    return function(num){
        const effects = dummyData.splice(index,index+num);
        index = index+num;
        return effects;
    }
}
export const loadEffects = loadMoreEffects();

// action.type 지정해줄 상수들.
export const LOAD_EFFECTS_REQUEST = "LOAD_EFFECTS_REQUEST";
export const LOAD_EFFECTS_SUCCESS = "LOAD_EFFECTS_SUCCESS";
export const LOAD_EFFECTS_FAILURE = "LOAD_EFFECTS_FAILURE";

const reducer = (state=initialized,action) =>produce(state,(draft)=>{
    switch(action.type){
        case LOAD_EFFECTS_REQUEST: // load Effect 요청 보낼때 
            draft.loadEffectsLoading = true;
            draft.loadEffectsDone = false;
            break;
        case LOAD_EFFECTS_SUCCESS:// load Effect 요청 성공할때
            draft.loadEffectsDone = true;
            draft.loadEffectsLoading = false;
            draft.mainEffects = draft.mainEffects.concat(action.data);
						// 받아와진 새로운 effect데이터를 mainEffects에 추가한다.
            draft.hasMoreEffects = draft.mainEffects.length<16;
						// mainEffects 가 16개이상이면 더이상 request 호출안하게 막는다.
            break;
        case LOAD_EFFECTS_FAILURE:// load Effect 실패 할때
            draft.loadEffectsDone=false;
            draft.loadEffectsLoading=false;
            draft.loadEffectsError = action.error;
            break;
        default:
            return;
    }
})

export default reducer

2. Redux-saga 기본 설정

import { all,fork, takeLatest,put,delay} from "@redux-saga/core/effects";
import { LOAD_EFFECTS_REQUEST, 
					LOAD_EFFECTS_SUCCESS,
					LOAD_EFFECTS_FAILURE } from "../reducers/effect";
import {loadEffects} from '../reducers/effect'
// Effect 불러와주는 함수를 import 해준다.

function* loadEffect(action){//3
    try{
    yield delay(1000);  
		//백엔드 구축 안했을때 비동기 느낌 나기 위해서 1초딜레이 하고 실행.
    yield put({
        type:LOAD_EFFECTS_SUCCESS,
        data: loadEffects(4), // 성공하면 Effect 4개 더 불러오기
    })
    }catch(err){
        console.log(err);
        yield put({ // redux 액션으로 보내줌. put:dispatch라고 생각하면 편하다.
            type:LOAD_EFFECTS_FAILURE,
            error:err.response.data,
        })
    }
}

function* watchLoadEffects(){
    yield takeLatest(LOAD_EFFECTS_REQUEST,loadEffect);
}

export default function* effectSaga(){
    yield all([
        fork(watchLoadEffects),
    ])
}

3. index.js 인덱스 페이지

import Head from 'next/head'
import Header from '../components/Header'
import Items from '../components/Items'
import { useEffect } from 'react'
import {LOAD_EFFECTS_REQUEST } from '../reducers/effect'
import { useDispatch,useSelector} from 'react-redux'
export default function Home() {
  const dispatch = useDispatch();
 const {mainEffects,loadEffectsLoading,hasMoreEffects} = useSelector((state)=>state.effect);
//1.  
useEffect(()=>{
    dispatch({
      type:LOAD_EFFECTS_REQUEST
    })
  },[])
//2.
  useEffect(()=>{
    const handleScroll = ()=>{
      console.log(window.scrollY, document.documentElement.clientHeight, document.documentElement.scrollHeight);
      if(window.scrollY+document.documentElement.clientHeight>document.documentElement.scrollHeight-100){
        if(!loadEffectsLoading && hasMoreEffects){
          dispatch({
            type:LOAD_EFFECTS_REQUEST
          })
        }
      }
    }
    window.addEventListener('scroll',handleScroll);
    return ()=>{
      window.removeEventListener('scroll',handleScroll);
    }
  },[loadEffectsLoading])

  return (
    <>
    <Head>
      <title>Effect Shop</title>
      <link rel="preconnect" href="https://fonts.googleapis.com"/>
      <link rel="preconnect" crossOrigin href="https://fonts.gstatic.com"  />
      <link href="https://fonts.googleapis.com/css2?family=Mochiy+Pop+P+One&family=Rowdies:wght@700&display=swap" rel="stylesheet"/>
    </Head>
    <Header/>
    <section>
      <Items effects={mainEffects} isProfile={false}/>
    </section>
    </>
  )
}

먼저 index.js 페이지 들어올때

useEffect() 활용해서 처음 리로딩때 사용해서 처음 4개 effect 를 불러와준다

useEffect(()=>{
    dispatch({
      type:LOAD_EFFECTS_REQUEST
    })
  },[])
  useEffect(()=>{
    const handleScroll = ()=>{
      if(window.scrollY+document.documentElement.clientHeight>document.documentElement.scrollHeight-100){
        if(!loadEffectsLoading && hasMoreEffects){
          dispatch({
            type:LOAD_EFFECTS_REQUEST
          })
        }
      }
    }
    window.addEventListener('scroll',handleScroll);
    return ()=>{
      window.removeEventListener('scroll',handleScroll);
    }
  },[loadEffectsLoading])
  1. window.scrollY : 현재 scroll y위치
  2. document.documentElement.clientHeight: 실제클라이언트높이
  3. document.documentElement.scrollheight: 실제 페이지 전체 높이

현재scroll y 위치 + 실제 클라이언트높이 = 실제 페이지 전체높이라는것을 알 수 있습니다

if(window.scrollY+document.documentElement.clientHeight>document.documentElement.scrollHeight-100)

거의 밑에쯤 도착했을때 loadEffectsLoading 이 false 이면서 hasMoreEffects true 일때 다시 불러오도록 구현했다.

구현하면서 만난 에러

처음에 index.js 에서 스크롤 감지하는 함수를 구현할때 componentDidMount 안에다가 사용을 안했다. 그래서 서버에서 가져올때 window 가 뭔지 documnet가 뭔지 알수가 없다.
기존의 client-side rendering 방식에서는 client(즉 브라우저)에서 Javascript 코드를 실행시켜 페이지를 생성하였는데, Next.js는 각각에 페이지에 대해 미리 HTML을 생성하여 client에게 넘겨주게 됩니다. 그래서 webAPI, 메소드 들은 componentDidMount()(react Hook 에서 useEffect)안에다가 페이지 실제 로드할때 실행할 수 있게끔 구현해야한다.

구현영상

현재 구현중에 에러는 아이템들이 새로 로딩이될때 key값이 중복이되서 나오는 에러다 추후로 고쳐나갈 부분이다.

0개의 댓글